I want to fill a TreeTableView dynamically. I have created the view with Scenebuilder. In MyController I have a Draw button which by cliking calls a method, which makes a treetable. I have references set. when i click the button only columns are appear. The first column which is ought to show the treecolumn shows nothing and there is also no other value under columns. The TreeTableView works when I just use it alone and add it to scene, but now i want to use the values to fill the treeTable which is made by scene Builder. There is something else that i can not figure out. The columns are set correctly when comment out treeTable = new TreeTableView<>(root); which is under treeTable comment! otherwise the columns are not shown either.
That is what i want to see:
package controller;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import model.DataConstructor;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.TreeTableColumn.CellDataFeatures;
import javafx.util.Callback;
public class MainController implements Initializable {
private TreeItem<String> root = new TreeItem<>("Functions");
private DataConstructor dc = new DataConstructor();
#FXML
private TreeTableView<String> treeTable;
#Override
public void initialize(URL location, ResourceBundle resources) {
// TODO Auto-generated method stub
}
public void testDraw(ActionEvent event) {
drawTable();
}
private void drawTable() {
root.setExpanded(true);
Set<String> combinedKeys = new HashSet<>(dc.getCombiFunc().keySet());
Set<String> funcAllKeys = new HashSet<>(dc.getSortedfuncAll().keySet());
funcAllKeys.removeAll(dc.getCombiFunc().keySet());
for (List<String> value : dc.getCombiFunc().values()) {
funcAllKeys.removeAll(value);
}
for (String valueremained : funcAllKeys) {
ArrayList<String> tempNameId = new ArrayList<>();
tempNameId.add(dc.getSortedfuncAll().get(valueremained));
// all elements which are not in combined functions (They are all
// orphan)
root.getChildren().add(new TreeItem<String>(tempNameId.get(0)));
}
Set<String> keyFromcombined = new HashSet<>();
List<String> valueOfCombined = new ArrayList<String>();
for (Entry<String, List<String>> ent : dc.getCombiFunc().entrySet()) {
valueOfCombined.add(ent.getValue().get(0));
}
List<String> rootKeyList = new ArrayList<>();
for (String key : combinedKeys) {
if (!valueOfCombined.contains((key))) {
keyFromcombined.add(dc.getFuncAll().get(key));
rootKeyList.add(key);
}
}
String[] rootKeys = rootKeyList.toArray(new String[rootKeyList.size()]);
// ////////////////treetable////////////////////////////
treeTable = new TreeTableView<>(root);
Arrays.stream(rootKeys).forEach(
rootKey -> root.getChildren().add(
createTreeItem(dc.getCombiFunc(), rootKey)));
// ////////////////First column/////////////////////////
TreeTableColumn<String, String> firstColumn = new TreeTableColumn<>("");
treeTable.getColumns().add(firstColumn);// Tree column
firstColumn
.setCellValueFactory(new Callback<CellDataFeatures<String, String>, ObservableValue<String>>() {
public ObservableValue<String> call(
CellDataFeatures<String, String> p) {
return new ReadOnlyStringWrapper(p.getValue()
.getValue());
}
});
// //////////////////Rest Columns////////////////////////
for (Entry<String, String> ent : dc.getSortedAssignedOrg().entrySet()) {
TreeTableColumn<String, ArrayList<String>> col = new TreeTableColumn<>();
Label label = new Label(ent.getValue());
col.setGraphic(label);
label.setTooltip(new Tooltip(label.getText()));// tooltip for column
// headers
col.setPrefWidth(45);
//cell Value Factory////////////////////////
col.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<String, ArrayList<String>>, ObservableValue<ArrayList<String>>>() {
#Override
public ObservableValue<ArrayList<String>> call(
CellDataFeatures<String, ArrayList<String>> param) {
TreeMap<String, List<String>> temp = (TreeMap<String, List<String>>) dc
.getFuncTypeOrg().clone();
ArrayList<String> result = new ArrayList<>();
for (int i = 0; i < dc.getFuncTypeOrg().size(); i++) {
List<String> list = temp.firstEntry().getValue();
String key = temp.firstEntry().getKey();
// root.getChildren();
if (list.get(1).equals(param.getValue().getValue())
&& list.get(5).equals(label.getText())) {
result.add(0, list.get(2));// weight
// //////////////org combi TODO
for (Entry<String, Set<String>> ent : dc
.getCombiOrg().entrySet()) {
if (ent.getKey().contains(col.getText()))
for (Set<String> value : dc.getCombiOrg()
.values()) {
if (value.contains(col.getText()))
System.out.println(col.getText());
}
}
// ///////////////org combi
if (list.size() > 6) {
result.add(1, list.get(list.size() - 1));// color
result.add(2, list.get(6));// App component
}
else
result.add("white");
result.add("noOrg");
} else {
temp.remove(key);
}
}
return new ReadOnlyObjectWrapper<ArrayList<String>>(result);
}
});
// //////////////cellfactory/////////////////////////
col.setCellFactory(new Callback<TreeTableColumn<String, ArrayList<String>>, TreeTableCell<String, ArrayList<String>>>() {
#Override
public TreeTableCell<String, ArrayList<String>> call(
TreeTableColumn<String, ArrayList<String>> param) {
return new TreeTableCell<String, ArrayList<String>>() {
public void updateItem(ArrayList<String> item,
boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setStyle("");
setText("");
} else if (item.contains("Green")) {
float weightInt = Float.parseFloat(item.get(0));
float res = weightInt * 1;
String resString = Float.toString(res);
this.setStyle("-fx-background-color:green");
setTooltip(new Tooltip(item.get(2)));
setText(resString);
} else if (item.contains("yellow")) {
this.setStyle("-fx-background-color:yellow");
setTooltip(new Tooltip(item.get(2)));
setText("0");
} else if (item.contains("white")) {
setText("DD");
}
}
};
};
});
treeTable.getColumns().add(col);
}
// end for col
treeTable.setPrefWidth(1200);
treeTable.setPrefHeight(500);
treeTable.setShowRoot(false);
treeTable.setTableMenuButtonVisible(true);
}
/**
* Create a TreeItem for a TreeView from a set of data given the data and an
* identified root within the data.
*/
private TreeItem<String> createTreeItem(TreeMap<String, List<String>> data,
String rootKey) {
TreeItem<String> item = new TreeItem<>();
item.setValue(rootKey);
item.setExpanded(true);
List<String> childData = data.get(rootKey);
if (childData != null) {
childData.stream().map(child -> createTreeItem(data, child))
.collect(Collectors.toCollection(item::getChildren));
}
String valueName = item.getValue();
//String sorteV = dc.getSortedfuncAll().get(valueName);
item.setValue((dc.getSortedfuncAll().get(valueName)));
return item;
}
}
Your drawTable() method creates a new TreeTableView but I don't see any code where you add that TreeTableView to the UI, or remove the existing TreeTableView.
You probably want to replace
treeTable = new TreeTableView<>(root);
with
treeTable.setRoot(root);
Related
In JavaFx-14 resizeColumnToFitContent method a method to autosize columns to fit the size of the header and the column data was estabished.
In the main this works, at least for simple columns, but when I try to use a column whose cellFactory is set to CheckBoxTableCell.forTableColumn or when I try to create my own TableCell (as it happens for a filtered combobox) all I get is the default width (of 80), which is independent of either the header width or the data width.
Obviously in the case of the CheckBoxTableCell, the cell is almost always smaller than the header, but even here the column header is truncated. In the image below the first two columns seem to work as expected, but the Profession column has had both the header text and the row data text truncated, and in the case of the CheckBoxTableCell it is only the header (which should read "Enabled Test") which is truncated.
I presume that there is something that the default TableCell used by a simple TableColumn has that I need to add to my explicit cells, although it is has to see how I would add this to the .forTableColumn generated TableCell.
Code to show the problem with the checkbox column (in scala, sorry my Java is rather rusty these days):-
import scalafx.application.JFXApp3
import scalafx.application.JFXApp3.PrimaryStage
import scalafx.scene.Scene
import scalafx.scene.control.TableView
import scalafx.scene.control.TableColumn
import scalafx.scene.control.cell.CheckBoxTableCell
import scalafx.beans.property.BooleanProperty
import scalafx.beans.value.ObservableValue
import scalafx.collections.ObservableBuffer
import javafx.scene.control.TableColumnBase
import javafx.scene.control.skin.{NestedTableColumnHeader,TableColumnHeader,TableHeaderRow,TableViewSkin}
import scalafx.application.Platform
object TableColumnWidth extends JFXApp3 {
override def start(): Unit = {
stage = new PrimaryStage {
title = "Test Table Column Width Problem"
scene = new Scene {
val ob = ObservableBuffer[TableColumnData]()
val table = new TableView[TableColumnData](ob) {
def createDefaultSkin = new TableViewSkin(this) {
override protected def createTableHeaderRow:TableHeaderRow = {
new TableHeaderRow(this) {
override protected def createRootHeader:NestedTableColumnHeader = {
new NestedTableColumnHeader(null) {
override protected def createTableColumnHeader(col:TableColumnBase[_,_]) = {
if(col == null || col.getColumns.isEmpty || col == getTableColumn) new FormFxTableColumnHeader(col)
else new NestedTableColumnHeader(col)
}
}
}
}
}
}
columns ++= Seq{new TableColumn[TableColumnData,java.lang.Boolean](){
text = "Enabled Test"
cellValueFactory = { c =>
(new BooleanProperty(this,"Enabled",c.value.enabled) {
onChange{ (_,_,newValue) => c.value.enabled = newValue }
}).asInstanceOf[ObservableValue[java.lang.Boolean,java.lang.Boolean]]
}
cellFactory = CheckBoxTableCell.forTableColumn(this)
editable = true
resizable = false
}
}
editable = true
}
root = table
ob ++= Seq(TableColumnData("First",false),
TableColumnData("Second",false))
Platform.runLater(() -> {
val w = table.columns.map { col =>
// To find the TableColumnHeader we can use column.getStyleableNode as suggested by kleopatra on StackOverflow:-
// you get the header from column.getStyleableNode (took a moment, had to check if it's really implemented) – kleopatra Jul 1 at 20:46
col.getStyleableNode() match {
case mtch:FormFxTableColumnHeader => mtch.resizeCol()
case u => col.getWidth()
}
}.sum + 25
// if(width.value > w) {
table.prefWidth = w
table.minWidth = w
table.maxWidth = w
})
sizeToScene()
}
}
}
}
class FormFxTableColumnHeader(tc:TableColumnBase[_,_]) extends TableColumnHeader(tc) {
def resizeCol():Double = {
resizeColumnToFitContent(-1)
// println(s"in resizeCol for ${tc.getText()}, width ${tc.width.value}}")
tc.getWidth()
}
}
class TableColumnData(var name:String,var enabled:Boolean)
The header in this one will be truncated to "Enabled..."
Anyone have any ideas as to what is needed?
As already mentioned in the comments, I don't see any issues in using the resizeColumnToFitContent method.
Below is your example re-written in java (FX18). And it works as expected to resize the table.
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.Skin;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumnBase;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.skin.NestedTableColumnHeader;
import javafx.scene.control.skin.TableColumnHeader;
import javafx.scene.control.skin.TableHeaderRow;
import javafx.scene.control.skin.TableViewSkin;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FitTableViewToColumnsDemo extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ObservableList<Person> persons = FXCollections.observableArrayList();
persons.add(new Person("Bruce Willis", "bruce.willis#disaster.com", "Actor"));
persons.add(new Person("Liz Truss", "liz.truss#gov.uk", "Prime Minister"));
persons.add(new Person("Vincent van Gough", "vincent#sunflower.com", "Artist"));
TableColumn<Person, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(param -> param.getValue().nameProperty());
TableColumn<Person, String> emailCol = new TableColumn<>("Email");
emailCol.setCellValueFactory(param -> param.getValue().emailProperty());
TableColumn<Person, Boolean> enabledCol = new TableColumn<>("Enabled Test");
enabledCol.setCellValueFactory(param -> param.getValue().enabledProperty());
enabledCol.setCellFactory(CheckBoxTableCell.forTableColumn(enabledCol));
TableColumn<Person, String> profCol = new TableColumn<>("Profession");
profCol.setCellValueFactory(param -> param.getValue().professionProperty());
TableView<Person> tableView = new TableView<>() {
#Override
protected Skin<?> createDefaultSkin() {
return new MyTableViewSkin(this);
}
};
tableView.getColumns().addAll(nameCol, emailCol, profCol, enabledCol);
tableView.setItems(persons);
VBox root = new VBox();
root.getChildren().addAll(tableView);
VBox.setVgrow(tableView, Priority.ALWAYS);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.setTitle("FX18 FitTableViewToColumnsDemo");
primaryStage.show();
Platform.runLater(() -> {
double totalWidth = tableView.getColumns().stream().map(tc -> {
if (tc.getStyleableNode() instanceof FormFxTableColumnHeader header) {
return header.resizeCol();
} else {
return tc.getWidth();
}
}).mapToDouble(f -> f.doubleValue()).sum();
totalWidth = totalWidth + 25;
tableView.setPrefWidth(totalWidth);
tableView.setMinWidth(totalWidth);
tableView.setMaxWidth(totalWidth);
primaryStage.sizeToScene();
});
}
class MyTableViewSkin extends TableViewSkin<Person> {
public MyTableViewSkin(TableView<Person> tableView) {
super(tableView);
}
#Override
protected TableHeaderRow createTableHeaderRow() {
return new TableHeaderRow(this) {
#Override
protected NestedTableColumnHeader createRootHeader() {
return new NestedTableColumnHeader(null) {
#Override
protected TableColumnHeader createTableColumnHeader(TableColumnBase col) {
if (col == null || col.getColumns().isEmpty() || col == getTableColumn()) {
return new FormFxTableColumnHeader(col);
} else {
return new NestedTableColumnHeader(col);
}
}
};
}
};
}
}
class FormFxTableColumnHeader extends TableColumnHeader {
TableColumnBase tcb;
public FormFxTableColumnHeader(TableColumnBase tc) {
super(tc);
tcb = tc;
}
public double resizeCol() {
resizeColumnToFitContent(-1);
return tcb.getWidth();
}
}
class Person {
private StringProperty name = new SimpleStringProperty();
private StringProperty email = new SimpleStringProperty();
private StringProperty profession = new SimpleStringProperty();
private BooleanProperty enabled = new SimpleBooleanProperty();
public Person(String name1, String email1, String profession1) {
name.set(name1);
email.set(email1);
profession.set(profession1);
}
public StringProperty nameProperty() {
return name;
}
public StringProperty emailProperty() {
return email;
}
public BooleanProperty enabledProperty() {
return enabled;
}
public StringProperty professionProperty() {
return profession;
}
}
}
UPDATE :
I think I am now able to reproduce the issue if I set the columns resizable property to false. I think that should be the issue at your side as well.
If you want to set the columns to resizable false, I think you can remove the setting at the time of declaration and add it after the tableView width is computed (in Platform.runLater).. something like..
Platform.runLater(() -> {
double totalWidth = tableView.getColumns().stream().map(tc -> {
if (tc.getStyleableNode() instanceof FormFxTableColumnHeader header) {
return header.resizeCol();
} else {
return tc.getWidth();
}
}).mapToDouble(f -> f.doubleValue()).sum();
totalWidth = totalWidth + 25;
tableView.setPrefWidth(totalWidth);
tableView.setMinWidth(totalWidth);
tableView.setMaxWidth(totalWidth);
profCol.setResizable(false); \\ turn off resizable after the width is computed
enabledCol.setResizable(false);
primaryStage.sizeToScene();
});
I need to add color to row and column based on pnl value. How can I change the color of the row and how do I get pnl value for determining change in the color of the row.
TreeTableColumn<ClosedTradesPnL, String> symColumn = new TreeTableColumn<>("Symbol");
symColumn.setPrefWidth(100);
symColumn.setCellValueFactory(
(TreeTableColumn.CellDataFeatures<ClosedTradesPnL, String> param) ->
new ReadOnlyStringWrapper(param.getValue().getValue().getSymbol())
);
TreeTableColumn<ClosedTradesPnL, Date> expiryColumn =
new TreeTableColumn<>("Expiry Date");
expiryColumn.setPrefWidth(100);
expiryColumn.setCellValueFactory(
(TreeTableColumn.CellDataFeatures<ClosedTradesPnL, Date> param) ->
new ReadOnlyObjectWrapper(param.getValue().getValue().getExpiry_date())
);
TreeTableColumn<ClosedTradesPnL, String> pnlColumn =
new TreeTableColumn<>("PnL");
pnlColumn.setPrefWidth(100);
// pnlColumn.setStyle(" -fx-background-color: red ;");
// pnlColumn.setCellValueFactory(
// (TreeTableColumn.CellDataFeatures<ClosedTradesPnL, String> param) ->
// new ReadOnlyStringWrapper(param.getValue().getValue().getRealized_PNL())
// );
pnlColumn.setCellValueFactory(new Callback<CellDataFeatures<ClosedTradesPnL, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<ClosedTradesPnL, String> p) {
int foo = Integer.parseInt(p.getValue().getValue().getRealized_PNL().replace(",", "").replace(".", ""));
if( foo == 0){
System.out.println("color app"+p.getValue().getValue().getRealized_PNL());
pnlColumn.setStyle(" -fx-background-color: red ;");
}else{
pnlColumn.setStyle("-fx-background-color: white ;");
}
System.out.println(p.getValue().getValue().getRealized_PNL());
return new ReadOnlyObjectWrapper(p.getValue().getValue().getRealized_PNL());
}
});
TreeTableView<ClosedTradesPnL> treeTableView = new TreeTableView<>(root);
treeTableView.getColumns().setAll(symColumn, expiryColumn,pnlColumn);
// pnlColumn.setStyle("-fx-alignment: center-right;-fx-control-inner-background: slateblue;");
sceneRoot.getChildren().add(treeTableView);
// if (treeTableView.getRow(root)){treeTableView.setBackground(Background.RED);}
stage.setScene(scene);
stage.show();
}
This should be self explanatory, I've included a few comments.
import java.util.Date;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableRow;
import javafx.scene.control.TreeTableView;
import javafx.stage.Stage;
public class StyleTableRow extends Application {
#Override
public void start(Stage primaryStage) {
TreeTableView<ClosedTradesPnL> ttv = new TreeTableView<>(new TreeItem<>(new ClosedTradesPnL()));
for (int i=0;i<10;i++) ttv.getRoot().getChildren().add(new TreeItem<>(new ClosedTradesPnL()));
ttv.getRoot().setExpanded(true);
TreeTableColumn<ClosedTradesPnL, String> symColumn = new TreeTableColumn<>("Symbol");
symColumn.setCellValueFactory((param) ->
new ReadOnlyStringWrapper(param.getValue().getValue().getSymbol())
);
TreeTableColumn<ClosedTradesPnL, Date> expiryColumn = new TreeTableColumn<>("Expiry Date");
expiryColumn.setCellValueFactory((param) ->
new ReadOnlyObjectWrapper<>(param.getValue().getValue().getExpiry_date())
);
//use Number for type of data in column
TreeTableColumn<ClosedTradesPnL, Number> pnlColumn = new TreeTableColumn<>("PnL");
pnlColumn.setCellValueFactory((param) ->
new ReadOnlyDoubleWrapper(param.getValue().getValue().getRealized_PNL())
);
//now use a cellFactory to style the cell
//you can get the row and style it as well
pnlColumn.setCellFactory((TreeTableColumn<ClosedTradesPnL, Number> param) -> {
TreeTableCell cell = new TreeTableCell<ClosedTradesPnL, Number>(){
#Override
//by using Number we don't have to parse a String
protected void updateItem(Number item, boolean empty) {
super.updateItem(item, empty);
TreeTableRow<ClosedTradesPnL> ttr = getTreeTableRow();
if (item == null || empty){
setText(null);
ttr.setStyle("");
setStyle("");
} else {
ttr.setStyle(item.doubleValue() > 0
? "-fx-background-color:lightgreen"
: "-fx-background-color:pink");
setText(item.toString());
setStyle(item.doubleValue() > 0
? "-fx-background-color:green"
: "-fx-background-color:red");
}
}
};
return cell;
});
ttv.getColumns().setAll(symColumn, expiryColumn,pnlColumn);
Scene scene = new Scene(ttv, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
private static class ClosedTradesPnL{
private SimpleStringProperty symbol = new SimpleStringProperty("symbol");
private SimpleObjectProperty<Date> expiry_date = new SimpleObjectProperty<>(new Date(System.currentTimeMillis()));
private SimpleDoubleProperty realized_PNL= new SimpleDoubleProperty(Math.random()-0.5);
public String getSymbol() {return symbol.get();}
public Date getExpiry_date() {return expiry_date.get();}
public double getRealized_PNL() {return realized_PNL.get();}
}
}
I have two TableViews in the same scene that are closely related. I want to set up a listener such that when the user hovers a certain row in one table, the row with the same index in the other table is "hovered" as well.
I'm trying to solve this with a custom row factory tableView.setRowFactory(...). Inside the factory call(...) method I can toggle a CSS pseudo-class (.myclass:hover) on the target row, like:
row.hoverProperty().addListener((obs, o, n) -> {
myOtherTable.[get row here].pseudoClassStateChanged(PseudoClass.getPseudoClass("hover"), true);
});
As you can see in my factory method I have a reference to the second TableView object, myOtherTable. I guess I have to get hold of its TableRow objects to go ahead and set the pseudo class, but I can't figure out how.
Maybe is there a better way to do this?
Create a single property representing the index of the hovered row, and a PseudoClass:
IntegerProperty hoveredRowIndex = new SimpleIntegerProperty(-1);
PseudoClass appearHovered = PseudoClass.getPseudoClass("appear-hovered");
Now create a row factory that creates table rows that observe this value and their own index:
Callback<TableView<T>, TableCell<T>> rowFactory = tv -> {
TableRow<T> row = new TableRow<T>() {
private BooleanBinding shouldAppearHovered = Bindings.createBooleanBinding(
() -> getIndex() != -1 && getIndex() == hoveredRowIndex.get(), indexProperty(),
hoveredRowIndex);
{
shouldAppearHovered.addListener(
(obs, wasHovered, isNowHovered) -> pseudoClassStateChanged(appearHovered, isNowHovered));
hoverProperty().addListener((obs, wasHovered, isNowHovered) -> {
if (isNowHovered) {
hoveredRowIndex.set(getIndex());
} else {
hoveredRowIndex.set(-1);
}
});
}
};
return row;
};
(Replace T with the actual type of the table.)
And now use the row factory for both tables. You can use the CSS selector
.table-row-cell:appear-hovered {
/* ... */
}
to style the rows that should appear to be hovered, or use
.table-row-cell:appear-hovered .table-cell {
/* ... */
}
to style individual cells in that row.
Here's a SSCCE:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class ConnectedHoverTables extends Application {
private IntegerProperty hoveredRowIndex = new SimpleIntegerProperty(-1);
private PseudoClass appearHovered = PseudoClass.getPseudoClass("appear-hovered");
#Override
public void start(Stage primaryStage) {
HBox root = new HBox(10, createTable(), createTable());
root.setPadding(new Insets(20));
Scene scene = new Scene(root);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private TableView<Item> createTable() {
TableView<Item> table = new TableView<>();
table.setRowFactory(tv -> {
TableRow<Item> row = new TableRow<Item>() {
private BooleanBinding shouldAppearHovered = Bindings.createBooleanBinding(
() -> getIndex() != -1 && getIndex() == hoveredRowIndex.get(), indexProperty(),
hoveredRowIndex);
{
shouldAppearHovered.addListener(
(obs, wasHovered, isNowHovered) -> pseudoClassStateChanged(appearHovered, isNowHovered));
hoverProperty().addListener((obs, wasHovered, isNowHovered) -> {
if (isNowHovered) {
hoveredRowIndex.set(getIndex());
} else {
hoveredRowIndex.set(-1);
}
});
}
};
return row;
});
table.setOnMouseClicked(e -> System.gc());
table.getColumns().add(column("Item", Item::nameProperty));
table.getColumns().add(column("Value", Item::valueProperty));
table.getItems().setAll(createData());
return table;
}
private List<Item> createData() {
Random rng = new Random();
List<Item> items = new ArrayList<>();
for (int i = 1; i <= 100; i++) {
Item item = new Item("Item " + i, rng.nextInt(1000));
items.add(item);
}
return items;
}
private <S, T> TableColumn<S, T> column(String title, Function<S, ObservableValue<T>> property) {
TableColumn<S, T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
return col;
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public Item(String name, int value) {
setName(name);
setValue(value);
}
public final StringProperty nameProperty() {
return this.name;
}
public final String getName() {
return this.nameProperty().get();
}
public final void setName(final String name) {
this.nameProperty().set(name);
}
public final IntegerProperty valueProperty() {
return this.value;
}
public final int getValue() {
return this.valueProperty().get();
}
public final void setValue(final int value) {
this.valueProperty().set(value);
}
}
public static void main(String[] args) {
launch(args);
}
}
If I remember correctly, you can't access directly a row of a TableView. The only way to get the row index is to access the attribute indexProperty when you define the CellFactory.
I advise you to create rather a personalized extending TableRow or TableCell object where you can stock an id or something like that...
How do I get the name of the column of a textfield inside a javaFX table?
I need this to check the value of the cells only in the "text2" column. I tried it with textfield.parent() but I didn't get a useful result.Edit: I just removed some unnessary log, which was not helpful for understanding.Now it is more convenient.
Here is my Code:
import java.util.ArrayList;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextArea;
import javafx.util.Callback;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/*interface inside_table
{
public String get_column_name
}*/
public class Supermain extends Application {
#Override
public void start(Stage primaryStage) {
ArrayList myindizes=new ArrayList();
final TableView<myTextRow> table = new TableView<>();
table.setEditable(true);
table.setStyle("-fx-text-wrap: true;");
//Table columns
TableColumn<myTextRow, String> clmID = new TableColumn<>("ID");
clmID.setMinWidth(160);
clmID.setCellValueFactory(new PropertyValueFactory<>("ID"));
TableColumn<myTextRow, String> clmtext = new TableColumn<>("Text");
clmtext.setMinWidth(160);
clmtext.setCellValueFactory(new PropertyValueFactory<>("text"));
clmtext.setCellFactory(new TextFieldCellFactory());
TableColumn<myTextRow, String> clmtext2 = new TableColumn<>("Text2");
clmtext2.setMinWidth(160);
clmtext2.setCellValueFactory(new PropertyValueFactory<>("text2"));
clmtext2.setCellFactory(new TextFieldCellFactory());
//Add data
final ObservableList<myTextRow> data = FXCollections.observableArrayList(
new myTextRow(5, "Lorem","bla"),
new myTextRow(2, "Ipsum","bla")
);
table.getColumns().addAll(clmID, clmtext,clmtext2);
table.setItems(data);
HBox hBox = new HBox();
hBox.setSpacing(5.0);
hBox.setPadding(new Insets(5, 5, 5, 5));
Button btn = new Button();
btn.setText("Get Data");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
for (myTextRow data1 : data) {
System.out.println("data:" + data1.getText2());
}
}
});
hBox.getChildren().add(btn);
BorderPane pane = new BorderPane();
pane.setTop(hBox);
pane.setCenter(table);
primaryStage.setScene(new Scene(pane, 640, 480));
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
public static class TextFieldCellFactory
implements Callback<TableColumn<myTextRow, String>, TableCell<myTextRow, String>> {
#Override
public TableCell<myTextRow, String> call(TableColumn<myTextRow, String> param) {
TextFieldCell textFieldCell = new TextFieldCell();
return textFieldCell;
}
public static class TextFieldCell extends TableCell<myTextRow, String> {
private TextArea textField;
private StringProperty boundToCurrently = null;
private String last_text;
public TextFieldCell() {
textField = new TextArea();
textField.setWrapText(true);
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
last_text="";
this.setGraphic(textField);
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
//only if textfield is in the text2 column
if(isNowFocused){last_text=textField.getText(); System.out.println("NOW focus "+last_text);}
if (! isNowFocused && ! isValid(textField.getText())) {
textField.setText(last_text);
textField.selectAll();
System.out.println("blur");
}
});
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
// Show the Text Field
this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
// myindizes.add(getIndex());
// Retrieve the actual String Property that should be bound to the TextField
// If the TextField is currently bound to a different StringProperty
// Unbind the old property and rebind to the new one
ObservableValue<String> ov = getTableColumn().getCellObservableValue(getIndex());
SimpleStringProperty sp = (SimpleStringProperty) ov;
if (this.boundToCurrently == null) {
this.boundToCurrently = sp;
this.textField.textProperty().bindBidirectional(sp);
} else if (this.boundToCurrently != sp) {
this.textField.textProperty().unbindBidirectional(this.boundToCurrently);
this.boundToCurrently = sp;
this.textField.textProperty().bindBidirectional(this.boundToCurrently);
}
double height = real_lines_height(textField.getText(), this.getWidth(), 30, 22);
textField.setPrefHeight(height);
textField.setMaxHeight(height);
textField.setMaxHeight(Double.MAX_VALUE);
// if height bigger than the biggest height in the row
//-> change all heights of the row(textfields ()typeof textarea) to this height
// else leave the height as it is
//System.out.println("item=" + item + " ObservableValue<String>=" + ov.getValue());
//this.textField.setText(item); // No longer need this!!!
} else {
this.setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}//update
private boolean isValid(String s){
if(s.length()<7){return true;}
return false;
}
}
}
public class myTextRow {
private final SimpleIntegerProperty ID;
private final SimpleStringProperty text;
private final SimpleStringProperty text2;
public myTextRow(int ID, String text,String text2) {
this.ID = new SimpleIntegerProperty(ID);
this.text = new SimpleStringProperty(text);
this.text2 = new SimpleStringProperty(text2);
}
//setter
public void setID(int id) {
this.ID.set(id);
}
public void setText(String text) {
this.text.set(text);
}
public void setText2(String text) {
this.text2.set(text);
}
//getter
public int getID() {
return ID.get();
}
public String getText() {
return text.get();
}
public String getText2() {
return text2.get();
}
//properties
public StringProperty textProperty() {
return text;
}
public StringProperty text2Property() {
return text2;
}
public IntegerProperty IDProperty() {
return ID;
}
}
private static double real_lines_height(String s, double width, double heightCorrector, double widthCorrector) {
HBox h = new HBox();
Label l = new Label("Text");
h.getChildren().add(l);
Scene sc = new Scene(h);
l.applyCss();
double line_height = l.prefHeight(-1);
int new_lines = s.replaceAll("[^\r\n|\r|\n]", "").length();
// System.out.println("new lines= "+new_lines);
String[] lines = s.split("\r\n|\r|\n");
// System.out.println("line count func= "+ lines.length);
int count = 0;
//double rest=0;
for (int i = 0; i < lines.length; i++) {
double text_width = get_text_width(lines[i]);
double plus_lines = Math.ceil(text_width / (width - widthCorrector));
if (plus_lines > 1) {
count += plus_lines;
//rest+= (text_width / (width-widthCorrector)) - plus_lines;
} else {
count += 1;
}
}
//count+=(int) Math.ceil(rest);
count += new_lines - lines.length;
return count * line_height + heightCorrector;
}
private static double get_text_width(String s) {
HBox h = new HBox();
Label l = new Label(s);
l.setWrapText(false);
h.getChildren().add(l);
Scene sc = new Scene(h);
l.applyCss();
return l.prefWidth(-1);
}
}
There are probably (way) better ways to organize this, but probably the cleanest fix is just to define a boolean validate parameter to the constructor of your cell implementation. (You really don't want the logic to be "if the title of the column is equal to some specific text, then validate". You would be utterly screwed when your boss came in to the office and asked you to internationalize the application, or even just change the title of the column, for example.)
Using an entire inner class just to implement the callback seems completely redundant, but keeping that you would have to pass the parameter through it:
public static class TextFieldCellFactory
implements Callback<TableColumn<myTextRow, String>, TableCell<myTextRow, String>> {
private final boolean validate ;
public TextFieldCellFactory(boolean validate) {
this.validate = validate ;
}
#Override
public TableCell<myTextRow, String> call(TableColumn<myTextRow, String> param) {
TextFieldCell textFieldCell = new TextFieldCell(validate);
return textFieldCell;
}
public static class TextFieldCell extends TableCell<myTextRow, String> {
private TextArea textField;
private StringProperty boundToCurrently = null;
private String last_text;
public TextFieldCell(boolean validate) {
textField = new TextArea();
textField.setWrapText(true);
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
last_text="";
this.setGraphic(textField);
if (validate) {
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
//only if textfield is in the text2 column
if(isNowFocused){last_text=textField.getText(); System.out.println("NOW focus "+last_text);}
if (! isNowFocused && ! isValid(textField.getText())) {
textField.setText(last_text);
textField.selectAll();
System.out.println("blur");
}
});
}
}
// ...
}
Then of course you just do
TableColumn<myTextRow, String> clmtext = new TableColumn<>("Text");
clmtext.setMinWidth(160);
clmtext.setCellValueFactory(new PropertyValueFactory<>("text"));
clmtext.setCellFactory(new TextFieldCellFactory(false));
TableColumn<myTextRow, String> clmtext2 = new TableColumn<>("Text2");
clmtext2.setMinWidth(160);
clmtext2.setCellValueFactory(new PropertyValueFactory<>("text2"));
clmtext2.setCellFactory(new TextFieldCellFactory(true));
(To properly answer your question, you can get the text of the column from within the cell to which it is attached with getTableColumn().getText(), but as I pointed out, actually basing the logic on the value displayed in a column header will make your code completely unmaintainable.)
And I guess for completeness, I should also mention that your TextFieldCellFactory class looks like it is not really serving any purpose. I would remove it entirely and just have the TextFieldCell class, and do
TableColumn<myTextRow, String> clmtext = new TableColumn<>("Text");
clmtext.setMinWidth(160);
clmtext.setCellValueFactory(new PropertyValueFactory<>("text"));
clmtext.setCellFactory(c -> new TextFieldCell(false));
TableColumn<myTextRow, String> clmtext2 = new TableColumn<>("Text2");
clmtext2.setMinWidth(160);
clmtext2.setCellValueFactory(new PropertyValueFactory<>("text2"));
clmtext2.setCellFactory(c -> new TextFieldCell(true));
I need to change the table row color using a property that would not be visible in any column of a tableview. I did the following:
create a model class Person (serialNumber, first, last).
create an observableList of Person using an extractor.
create two tableviews(tableview1, tableview2) and one listview that all sharing the same data.
tableview1 has a serialCol1 column with a visible property set to
false.
I want to change tableview1 row color using the serialNumber property that is bound to a column in a tableview2.
Here is the complete program:
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
/**
*
* #author kachna
*/
public class Extractor extends Application {
private final TableView<Person> tableView1 = new TableView<>();
private final TableView<Person> tableView2 = new TableView<>();
private final ListView<Person> listView = new ListView<>();
//observable list with extractor
private final ObservableList<Person> data = FXCollections.observableArrayList(p -> new Observable[]{p.serialNumberProperty(), p.firstProperty(), p.lastProperty()});
static class Person {
final IntegerProperty serialNumber;
final StringProperty first;
final StringProperty last;
public Person(int serialNumber, String first, String last) {
this.first = new SimpleStringProperty(first);
this.last = new SimpleStringProperty(last);
this.serialNumber = new SimpleIntegerProperty(serialNumber);
}
public IntegerProperty serialNumberProperty() {
return serialNumber;
}
public StringProperty firstProperty() {
return first;
}
public StringProperty lastProperty() {
return last;
}
#Override
public String toString() {
return "Person{" + "first=" + first.get() + ", last=" + last.get() + '}';
}
}
#Override
public void start(Stage stage) {
BorderPane root = new BorderPane();
VBox vBox = new VBox(10);
VBox.setVgrow(tableView2, Priority.ALWAYS);
root.setPadding(new Insets(10));
initTableViews();
initListView();
getData();
Label label1 = new Label("TableView 1");
label1.setStyle("-fx-font-size: 24px;\n"
+ "-fx-font-weight: bold;");
Label label2 = new Label("TableView 2");
label2.setStyle("-fx-font-size: 24px;\n"
+ "-fx-font-weight: bold;");
vBox.getChildren().addAll(label1, tableView1,label2, tableView2);
root.setCenter(vBox);
root.setRight(listView);
Scene scene = new Scene(root, 600, 400);
stage.setScene(scene);
stage.show();
}
private void initTableViews() {
// first table view
tableView1.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
tableView1.setEditable(true);
tableView1.setRowFactory(tv -> new TableRow<Person>() {
#Override
protected void updateItem(Person item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
if (item.serialNumber.get() % 2 == 0) {
setStyle("-fx-background-color: orange;");
} else {
setStyle(" ");
}
} else {
setStyle(" ");
}
}
});
TableColumn<Person, Number> serialCol1 = new TableColumn<>("Serial Number");
serialCol1.setCellValueFactory(cellData -> cellData.getValue().serialNumberProperty());
serialCol1.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<Number>() {
#Override
public String toString(Number object) {
return object.toString();
}
#Override
public Number fromString(String string) {
return Integer.parseInt(string);
}
}));
// make the serialCol1 column invisible
serialCol1.setVisible(false);
TableColumn<Person, String> firstCol1 = new TableColumn<>("First Name");
firstCol1.setCellValueFactory(cellData -> cellData.getValue().firstProperty());
firstCol1.setCellFactory(TextFieldTableCell.forTableColumn());
TableColumn<Person, String> lastCol1 = new TableColumn<>("Last Name");
lastCol1.setCellFactory(TextFieldTableCell.forTableColumn());
lastCol1.setCellValueFactory(cellData -> cellData.getValue().lastProperty());
tableView1.getColumns().addAll(serialCol1, firstCol1, lastCol1);
tableView1.setItems(data);
// second table view
tableView2.setEditable(true);
tableView2.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
TableColumn<Person, Number> serialCol = new TableColumn<>("Serial Number");
serialCol.setCellValueFactory(cellData -> cellData.getValue().serialNumberProperty());
serialCol.setCellFactory(TextFieldTableCell.forTableColumn(new StringConverter<Number>() {
#Override
public String toString(Number object) {
return object.toString();
}
#Override
public Number fromString(String string) {
return Integer.parseInt(string);
}
}));
TableColumn<Person, String> firstCol2 = new TableColumn<>("First Name");
firstCol2.setCellValueFactory(cellData -> cellData.getValue().firstProperty());
TableColumn<Person, String> lastCol2 = new TableColumn<>("Last Name");
lastCol2.setCellFactory(TextFieldTableCell.forTableColumn());
lastCol2.setCellValueFactory(cellData -> cellData.getValue().lastProperty());
tableView2.getColumns().addAll(serialCol, firstCol2, lastCol2);
tableView2.setItems(data);
}
private void initListView() {
//list view
listView.setCellFactory(list -> new ListCell<Person>() {
#Override
protected void updateItem(Person value, boolean empty) {
super.updateItem(value, empty);
if (!empty && value != null) {
if (value.serialNumber.get() % 2 == 0) {
setStyle("-fx-background-color: orange;");
} else {
setStyle(" ");
}
setText(String.format("%s %s %s", value.serialNumber.get(), value.firstProperty().get(), value.lastProperty().get()));
} else {
setText(null);
setStyle(" ");
}
}
});
listView.setItems(data);
}
private void getData() {
data.setAll(IntStream.range(0, 10)
.mapToObj(i -> new Person(i, "first" + i, "last" + i))
.collect(Collectors.toList()));
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Problem:
the style of tableview1 doesn't change instantly after applying a
change. I have to scroll hrough the rows to see the style updated. the style of the listview is changed instantly without any problems.
The updateItem method is not bound to the property lifecycle of its item ( an item must not be an Observable ), but rather gets called by the View (ListView/TableView) whenever it deems it necessary to update the data representation. When you scroll a Row off screen it gets nulled ( I assume for performance reasons ) and updated again when in screen.
What you want to do is to bind the stylePropertyof the row to its items serialNumberPropertylike so:
tableView1.setRowFactory( tv -> new TableRow<Person>()
{
#Override
protected void updateItem( final Person item, final boolean empty )
{
super.updateItem( item, empty );
if ( !empty && item != null )
{
this.styleProperty().bind( Bindings.createStringBinding( () ->
{
if ( item.serialNumber.get() % 2 == 0 )
{
return "-fx-background-color: orange;";
}
return " ";
} , item.serialNumberProperty() ) );
}
else
{
/*
* As per comment in the Cell API
*/
setText( null );
setGraphic( null );
this.styleProperty().unbind();
setStyle( " " );
}
}
} );
I also recommend consulting the documentation of javafx.scene.control.Cell#updateitem(...) as it is marked as "Expert API".
Link to full example.