TableColumn tc = new TableColumn();
tc.getStyleClass.add(".style in css file")
I set up the TableColumn with a CSS file, and I want to give each cell different background colors. How can I accomplish this?
Example) TableColumn 1~5
TableColumn 1, row 3 has black and
TableColumn 5, row 4 has green ... etc
Create a cellFactory that selects the background color based on the column & row index.
Example:
TableView<Item<String>> tableView = new TableView<>();
// sample item class contains a single property with the same type as the type parameter (String in this case)
tableView.getItems().addAll(
new Item<>("1"),
new Item<>("2"),
new Item<>("4"),
new Item<>("5"),
new Item<>("6"),
new Item<>("7"),
new Item<>("8"),
new Item<>("9")
);
// create columns
TableColumn<Item<String>, String> column1 = new TableColumn<>("value");
TableColumn<Item<String>, Void> column2 = new TableColumn<>();
tableView.getColumns().addAll(column1, column2);
// create list of colors (CSS)
final List<String> colors = Arrays.asList(
"blue",
"green",
"red",
"violet",
"yellow",
...
);
Callback factory = new Callback<TableColumn<Item<String>, Object>, TableCell<Item<String>, Object>>() {
private int columns = tableView.getColumns().size();
#Override
public TableCell<Item<String>, Object> call(TableColumn<Item<String>, Object> param) {
return new TableCell<Item<String>, Object>() {
private int columnIndex = param.getTableView().getColumns().indexOf(param);
#Override
public void updateIndex(int i) {
super.updateIndex(i);
// select color based on index of row/column
if (i >= 0) {
// select color repeating the color, if we run out of colors
String color = colors.get((i * columns + columnIndex) % colors.size());
this.setStyle("-fx-my-cell-background: " + color + ";");
System.out.println(getStyle());
}
}
#Override
protected void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
// assign item's toString value as text
if (empty || item == null) {
setText(null);
} else {
setText(item.toString());
}
}
};
}
};
column1.setCellValueFactory(new PropertyValueFactory<>("value"));
column1.setCellFactory(factory);
column2.setCellFactory(factory);
Scene scene = new Scene(tableView);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
Valid CSS color strings are described here: https://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html#typecolor
style.css
.table-cell:filled {
-fx-background-color: -fx-my-cell-background;
}
.table-view:row-selection .table-row-cell:selected .table-cell {
-fx-background-color: null;
}
.table-view:cell-selection .table-cell:selected {
-fx-background-color: -fx-table-cell-border-color, -fx-background;
}
To change the behavior of specific cells in a TableView you need can set the cellFactory of the TableColumns. Here's an example:
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class TableMCVE extends Application {
#Override
public void start(Stage stage) {
ObservableList<ObservableList<String>> tableData = FXCollections.observableArrayList();
tableData.add(FXCollections.observableArrayList("Row1Col1", "Row1Col2"));
tableData.add(FXCollections.observableArrayList("Row2Col1", "Row2Col2"));
tableData.add(FXCollections.observableArrayList("Row3Col1", "Row3Col2"));
TableView<ObservableList<String>> table = new TableView<ObservableList<String>>();
TableColumn<ObservableList<String>, String> col1 = new TableColumn<ObservableList<String>, String>("Col1");
col1.setCellValueFactory(e -> new SimpleStringProperty(e.getValue().get(0)));
// Set the cell factory of the column with a custom TableCell to modify its behavior.
col1.setCellFactory(e -> new TableCell<ObservableList<String>, String>() {
#Override
public void updateItem(String item, boolean empty) {
// Always invoke super constructor.
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else {
setText(item);
// If index is two we set the background color explicitly.
if (getIndex() == 2) {
this.setStyle("-fx-background-color: green;");
}
}
}
});
TableColumn<ObservableList<String>, String> col2 = new TableColumn<ObservableList<String>, String>("Col2");
col2.setCellValueFactory(e -> new SimpleStringProperty(e.getValue().get(1)));
// Set the cell factory of the column with a custom TableCell to modify its behavior.
col2.setCellFactory(e -> new TableCell<ObservableList<String>, String>() {
#Override
public void updateItem(String item, boolean empty) {
// Always invoke super constructor.
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else {
setText(item);
// If index is zero we set the background color explicitly.
if (getIndex() == 0) {
this.setStyle("-fx-background-color: blue;");
}
}
}
});
table.getColumns().addAll(col1, col2);
table.getItems().addAll(tableData);
stage.setScene(new Scene(table));
stage.show();
}
public static void main(String[] args) {
launch();
}
}
In my example I just set the color based on the index the cell, but you can change that to something a little more meaningful obviously. E.g. the color could be based on the value of the cell.
Related
In TreeTableView I need to find and setStyle rows with NO children.
In below code example, problematic code is in method: markRows.
public class Controller {
public TreeTableView<MyTreeObject> fuses_ttv;
private ArrayList<MyTreeObject> data = new ArrayList<>();
private void createTreeTableView(){}
private void markRows(){
fuses_ttv.setRowFactory(row -> new TreeTableRow<MyTreeObject>(){
#Override
protected void updateItem(MyTreeObject item, boolean empty) {
super.updateItem(item, empty);
if (item==null){
setStyle(null);
} else if (item.getType().equals("FRC")){
setStyle("-fx-background-color: lightslategray;");
} else if(item.getType().equals("wire")){
setStyle("-fx-background-color: lightyellow;");
} //***** else if (ROW HAS NOW CHILDREN) - HOW TO DO IT????? ******
}
});
}
}
Like in picture below - rows with SLOT "A1" and "A2" have no children.
How to identify such rows?
Thanks in advance for any help.
In JavaFX 19 and later you can do:
fuses_ttv.setRowFactory(row -> new TreeTableRow<MyTreeObject>(){
{
treeItemProperty().flatMap(TreeItem::leafProperty)
.orElse(false)
.addListener((obs, wasLeaf, isLeaf) -> {
if (isLeaf) {
// set style for leaf (no children)
} else {
// set style for non-leaf (has children)
}
});
}
#Override
protected void updateItem(MyTreeObject item, boolean empty) {
super.updateItem(item, empty);
if (item==null){
setStyle(null);
} else if (item.getType().equals("FRC")){
setStyle("-fx-background-color: lightslategray;");
} else if(item.getType().equals("wire")){
setStyle("-fx-background-color: lightyellow;");
} //***** else if (ROW HAS NOW CHILDREN) - HOW TO DO IT????? ******
}
});
I would actually recommend setting custom PseudoClasses, and an external style sheet, instead of using inline styles.
Here is a complete working example:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.css.PseudoClass;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class TreeTableStyleExample extends Application {
private int itemCount ;
#Override
public void start(Stage stage) throws IOException {
TreeTableView<Integer> table = new TreeTableView<>();
TreeTableColumn<Integer, Number> column = new TreeTableColumn<>("Item");
table.getColumns().add(column);
column.setCellValueFactory(data -> new SimpleIntegerProperty(data.getValue().getValue()));
column.setCellFactory(ttv -> new TreeTableCell<>() {
#Override
protected void updateItem(Number item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText("");
} else {
setText("Item "+item);
}
}
});
PseudoClass leaf = PseudoClass.getPseudoClass("leaf");
PseudoClass odd = PseudoClass.getPseudoClass("odd-value");
PseudoClass even = PseudoClass.getPseudoClass("even-value");
table.setRowFactory( ttv -> new TreeTableRow<>() {
{
treeItemProperty().flatMap(TreeItem::leafProperty).orElse(false)
.addListener((obs, wasLeaf, isNowLeaf) -> pseudoClassStateChanged(leaf, isNowLeaf));
}
#Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
pseudoClassStateChanged(odd, false);
pseudoClassStateChanged(even, false);
} else {
pseudoClassStateChanged(odd, item % 2 == 1);
pseudoClassStateChanged(even, item % 2 == 0);
}
}
});
table.setRoot(buildTable(20));
Button add = new Button("Add item");
add.disableProperty().bind(Bindings.isEmpty(table.getSelectionModel().getSelectedItems()));
add.setOnAction(e -> {
TreeItem<Integer> treeItem = new TreeItem<>(++itemCount);
treeItem.setExpanded(true);
table.getSelectionModel().getSelectedItem().getChildren().add(treeItem);
});
Button remove = new Button("Remove");
remove.disableProperty().bind(Bindings.isEmpty(table.getSelectionModel().getSelectedItems())
.or(Bindings.equal(table.getSelectionModel().selectedItemProperty(), table.getRoot())));
remove.setOnAction(e -> {
TreeItem<Integer> selection = table.getSelectionModel().getSelectedItem();
selection.getParent().getChildren().remove(selection);
});
HBox controls = new HBox(5, add, remove);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(5));
BorderPane root = new BorderPane(table);
root.setBottom(controls);
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
private TreeItem<Integer> buildTable(int numItems) {
Random rng = new Random();
TreeItem<Integer> root = new TreeItem<>(1);
root.setExpanded(true);
List<TreeItem> items = new ArrayList<>();
items.add(root);
for (itemCount = 2; itemCount <= numItems ; itemCount++) {
TreeItem<Integer> item = new TreeItem<>(itemCount);
item.setExpanded(true);
items.get(rng.nextInt(items.size())).getChildren().add(item);
items.add(item);
}
return root ;
}
public static void main(String[] args) {
launch();
}
}
with style.css:
.tree-table-row-cell:odd-value {
-fx-background: lightslategray ;
}
.tree-table-row-cell:even-value {
-fx-background: lightyellow;
}
.tree-table-row-cell:leaf {
-fx-background: lightgreen ;
}
Sample output:
I have a TableView where every row has a ContextMenu like in the image below.
When I click on the first MenuItem called ("Contrassegna riga come analizzata"), I want all selected rows of the TableView (in the example above the ones starting with 22002649 and 22016572) to change color.
If they are already coloured, I want them to remove it.
I tried with the following code but it obviously works only with the last selected row and not with others
tableView.setRowFactory(
new Callback<TableView, TableRow>() {
#Override
public TableRow call(TableView tableView0) {
final TableRow row = new TableRow<>();
final ContextMenu rowMenu = new ContextMenu();
final PseudoClass checkedPC = PseudoClass.getPseudoClass("checked");
MenuItem doneRiga = new MenuItem("Contrassegna riga come analizzata");
doneRiga.setOnAction(j -> {
if (!row.getPseudoClassStates().contains(checkedPC))
row.pseudoClassStateChanged(checkedPC, true);
else
row.pseudoClassStateChanged(checkedPC, false);
});
MenuItem doneArticolo = new MenuItem("Contrassegna articolo come analizzato");
rowMenu.getItems().addAll(doneRiga, doneArticolo);
return row;
}
});
Consequently I obtain the following result
Any suggestions? Thank you
This is really a duplicate of Programmatically change the TableView row appearance, but since that question is quite old, here is a solution using more modern Java idioms.
Typically your model class should contain observable properties for all data that is required to view it. In this case, your table items can be either "analyzed" or "not analyzed", so they would usually have a boolean property to represent that. For example:
public class Item {
private final StringProperty name = new SimpleStringProperty();
private final BooleanProperty analyzed = new SimpleBooleanProperty();
public Item(String name) {
setName(name);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public boolean getAnalyzed() {
return analyzed.get();
}
public BooleanProperty analyzedProperty() {
return analyzed;
}
public void setAnalyzed(boolean analyzed) {
this.analyzed.set(analyzed);
}
}
Your table row needs to do two things:
Observe the analyzedProperty() of the current item it is displaying, so it updates the state if that property changes. Note this mean if the item changes, it needs to remove a listener from the old item (i.e. stop observing the property in the old item) and add a listener to the new item (start observing the property in the new item).
If the item changes, update the state of the row to reflect the analyzed state of the new item.
A table row implementation that does this looks like:
TableRow<Item> row = new TableRow<>(){
private final ChangeListener<Boolean> analyzedListener = (obs, wasAnalyzed, isNowAnalyzed) ->
updateState(isNowAnalyzed);
{
// Make sure we are observing the analyzedProperty on the current item
itemProperty().addListener((obs, oldItem, newItem) -> {
if (oldItem != null) {
oldItem.analyzedProperty().removeListener(analyzedListener);
}
if (newItem != null) {
newItem.analyzedProperty().addListener(analyzedListener);
}
});
}
#Override
protected void updateItem(Item item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
updateState(false);
} else {
updateState(item.getAnalyzed());
}
}
private void updateState(boolean analyzed) {
pseudoClassStateChanged(analyzedPC, analyzed);
}
};
Note that in JavaFX 19 you can use the flatMap() API to simplify this code considerably:
TableRow<Item> row = new TableRow<>();
row.itemProperty()
.flatMap(Item::analyzedProperty)
.orElse(false)
.addListener((obs, wasAnalyzed, isNowAnalyzed) -> {
row.pseudoClassStateChanged(analyzedPC, isNowAnalyzed);
});
Now to change the state of the selected items, you just need to iterate through them and toggle the analyzed state:
ContextMenu menu = new ContextMenu();
MenuItem analyzedMI = new MenuItem("Analyzed");
analyzedMI.setOnAction(e -> {
// Toggle analyzed state of selected items
List<Item> selectedItems = row.getTableView().getSelectionModel().getSelectedItems();
for (Item item : selectedItems) {
item.setAnalyzed(! item.getAnalyzed());
}
});
menu.getItems().add(analyzedMI);
row.setContextMenu(menu);
Putting it all together in a complete example:
package org.jamesd.examples.highlightrows;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.List;
public class HelloApplication extends Application {
private static final PseudoClass analyzedPC = PseudoClass.getPseudoClass("analyzed");
#Override
public void start(Stage stage) throws IOException {
TableView<Item> table = new TableView<>();
TableColumn<Item, String> column = new TableColumn<>("Item");
column.setCellValueFactory(data -> data.getValue().nameProperty());
table.getColumns().add(column);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
table.setRowFactory(tc -> {
TableRow<Item> row = new TableRow<>();
row.itemProperty()
.flatMap(Item::analyzedProperty)
.orElse(false)
.addListener((obs, wasAnalyzed, isNowAnalyzed) -> {
row.pseudoClassStateChanged(analyzedPC, isNowAnalyzed);
});
// Prior to JavaFX 19 you need something like the following (which is probably less robust):
// TableRow<Item> row = new TableRow<>(){
// private final ChangeListener<Boolean> analyzedListener = (obs, wasAnalyzed, isNowAnalyzed) ->
// updateState(isNowAnalyzed);
//
// {
// // Make sure we are observing the analyzedProperty on the current item
// itemProperty().addListener((obs, oldItem, newItem) -> {
// if (oldItem != null) {
// oldItem.analyzedProperty().removeListener(analyzedListener);
// }
// if (newItem != null) {
// newItem.analyzedProperty().addListener(analyzedListener);
// }
// });
// }
// #Override
// protected void updateItem(Item item, boolean empty) {
// super.updateItem(item, empty);
// if (empty || item == null) {
// updateState(false);
// } else {
// updateState(item.getAnalyzed());
// }
// }
//
// private void updateState(boolean analyzed) {
// pseudoClassStateChanged(analyzedPC, analyzed);
// }
// };
ContextMenu menu = new ContextMenu();
MenuItem analyzedMI = new MenuItem("Analyzed");
analyzedMI.setOnAction(e -> {
// Toggle analyzed state of selected items
List<Item> selectedItems = row.getTableView().getSelectionModel().getSelectedItems();
for (Item item : selectedItems) {
item.setAnalyzed(! item.getAnalyzed());
}
});
menu.getItems().add(analyzedMI);
row.setContextMenu(menu);
return row;
});
for (int i = 1 ; i <= 20 ; i++) {
table.getItems().add(new Item("Item "+i));
}
BorderPane root = new BorderPane(table);
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final BooleanProperty analyzed = new SimpleBooleanProperty();
public Item(String name) {
setName(name);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public boolean getAnalyzed() {
return analyzed.get();
}
public BooleanProperty analyzedProperty() {
return analyzed;
}
public void setAnalyzed(boolean analyzed) {
this.analyzed.set(analyzed);
}
}
public static void main(String[] args) {
launch();
}
}
with the stylesheet style.css (in the same package as the application class):
.table-row-cell:analyzed {
-fx-control-inner-background: derive(green, 20%);
-fx-control-inner-background-alt: green;
-fx-selection-bar: #00b140;
}
If for some reason you cannot change the model class (Item in the code above), you need to track which items are "analyzed" separately in a way that can be observed. An ObservableList could be used for this:
final ObservableList<Item> analyzedItems = FXCollections.observableArrayList();
Now the table row can observe that list, and update the CSS pseudoclass if the list changes:
TableRow<Item> row = new TableRow<>(){
{
// If the list of analyzed items changes, make sure the state is correct:
analyzedItems.addListener((ListChangeListener.Change<? extends Item> change) -> {
updateState(analyzedItems.contains(getItem()));
});
}
#Override
protected void updateItem(Item item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
updateState(false);
} else {
updateState(analyzedItems.contains(item));
}
}
private void updateState(boolean analyzed) {
pseudoClassStateChanged(analyzedPC, analyzed);
}
};
and you can toggle the state by adding or removing items from the list of analyzed items accordingly:
ContextMenu menu = new ContextMenu();
MenuItem analyzedMI = new MenuItem("Analyze");
analyzedMI.setOnAction(e -> {
// Toggle analyzed state of selected items
List<Item> selectedItems = row.getTableView().getSelectionModel().getSelectedItems();
for (Item item : selectedItems) {
if (analyzedItems.contains(item)) {
analyzedItems.remove(item);
} else {
analyzedItems.add(item);
}
}
});
menu.getItems().add(analyzedMI);
row.setContextMenu(menu);
The complete example in this case looks like;
package org.jamesd.examples.highlightrows;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.List;
public class HelloApplication extends Application {
private static final PseudoClass analyzedPC = PseudoClass.getPseudoClass("analyzed");
#Override
public void start(Stage stage) throws IOException {
TableView<Item> table = new TableView<>();
TableColumn<Item, String> column = new TableColumn<>("Item");
column.setCellValueFactory(data -> data.getValue().nameProperty());
table.getColumns().add(column);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
final ObservableList<Item> analyzedItems = FXCollections.observableArrayList();
table.setRowFactory(tc -> {
TableRow<Item> row = new TableRow<>(){
{
// If the list of analyzed items changes, make sure the state is correct:
analyzedItems.addListener((ListChangeListener.Change<? extends Item> change) -> {
updateState(analyzedItems.contains(getItem()));
});
}
#Override
protected void updateItem(Item item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
updateState(false);
} else {
updateState(analyzedItems.contains(item));
}
}
private void updateState(boolean analyzed) {
pseudoClassStateChanged(analyzedPC, analyzed);
}
};
ContextMenu menu = new ContextMenu();
MenuItem analyzedMI = new MenuItem("Analyze");
analyzedMI.setOnAction(e -> {
// Toggle analyzed state of selected items
List<Item> selectedItems = row.getTableView().getSelectionModel().getSelectedItems();
for (Item item : selectedItems) {
if (analyzedItems.contains(item)) {
analyzedItems.remove(item);
} else {
analyzedItems.add(item);
}
}
});
menu.getItems().add(analyzedMI);
row.setContextMenu(menu);
return row;
});
for (int i = 1 ; i <= 20 ; i++) {
table.getItems().add(new Item("Item "+i));
}
BorderPane root = new BorderPane(table);
Scene scene = new Scene(root);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());
stage.setScene(scene);
stage.show();
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
public Item(String name) {
setName(name);
}
public String getName() {
return name.get();
}
public StringProperty nameProperty() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
}
public static void main(String[] args) {
launch();
}
}
What's the easiest way to have a TreeView's cells auto-refresh with a new style when a condition is met in a separate TableView?
I'm currently setting the TreeCells' styles in the updateItem() method in the TreeView cell factory, but this only fires off if the user adds or removes something in the TreeView. I want to be able to change the style of a given TreeCell if I check off all 3 checkboxes in a separate dialog box.
I'm currently able to monitor the number of checked checkboxes with a BooleanProperty and an IntegerProperty, but I have no idea how I'm supposed to "auto-update" or call a TreeView refresh when a TreeItem's Object's BooleanProperty changes.
Any help is greatly appreciated.
You can set the style in the TreeCell whenever a boolean property on the value underlying the TreeCell is updated (via a binding).
return new TreeCell<Message>() {
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
styleProperty().unbind();
if (empty || item == null || item.getText() == null) {
setText(null);
styleProperty.set(null);
} else {
setText(item.getText());
styleProperty().bind(
Bindings.when(
item.readProperty()
).then("-fx-background-color: red;")
.otherwise("-fx-background-color: null;")
);
}
}
};
Full Sample
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TreeViewSample extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
ObservableList<Message> messages = FXCollections.observableArrayList();
TreeItem<Message> rootItem = new TreeItem<> (new Message("Inbox"));
rootItem.setExpanded(true);
for (int i = 1; i < 6; i++) {
Message message = new Message("Message" + i);
messages.add(message);
TreeItem<Message> item = new TreeItem<> (message);
rootItem.getChildren().add(item);
}
TreeView<Message> tree = new TreeView<> (rootItem);
tree.setCellFactory(new Callback<TreeView<Message>, TreeCell<Message>>() {
#Override
public TreeCell<Message> call(TreeView<Message> param) {
return new TreeCell<Message>() {
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
styleProperty().unbind();
if (empty || item == null || item.getText() == null) {
setText(null);
styleProperty.set(null);
} else {
setText(item.getText());
styleProperty().bind(
Bindings.when(
item.readProperty()
).then("-fx-background-color: red;")
.otherwise("-fx-background-color: null;")
);
}
}
};
}
});
TableView<Message> tableView = new TableView<>();
tableView.setEditable(true);
TableColumn<Message, String> textCol = new TableColumn<>("Text");
textCol.setCellValueFactory(new PropertyValueFactory<>("text"));
tableView.getColumns().add(textCol);
TableColumn<Message, Boolean> readCol = new TableColumn<>("Read");
readCol.setCellValueFactory(new PropertyValueFactory<>("read"));
readCol.setCellFactory(CheckBoxTableCell.forTableColumn(readCol));
readCol.setEditable(true);
tableView.getColumns().add(readCol);
tableView.setItems(messages);
VBox root = new VBox(10, tree, tableView);
root.setPadding(new Insets(10));
stage.setScene(new Scene(root, 300, 250));
stage.show();
}
public class Message {
private StringProperty text = new SimpleStringProperty();
private BooleanProperty read = new SimpleBooleanProperty(false);
public Message(String msgText) {
text.set(msgText);
}
public String getText() {
return text.get();
}
public StringProperty textProperty() {
return text;
}
public void setText(String text) {
this.text.set(text);
}
public boolean isRead() {
return read.get();
}
public BooleanProperty readProperty() {
return read;
}
public void setRead(boolean read) {
this.read.set(read);
}
}
}
I'm trying to the bind the graphicProperty to the same BooleanProperty and change the image based on the value.
Example using a binding of an Image within an ImageView associated with the cell.
Image unreadImage = new Image("http://icons.iconarchive.com/icons/oxygen-icons.org/oxygen/16/Status-mail-unread-new-icon.png");
Image readImage = new Image("http://icons.iconarchive.com/icons/icons8/ios7/16/Messaging-Read-Message-icon.png");
. . .
return new TreeCell<Message>() {
ImageView imageView = new ImageView();
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
styleProperty().unbind();
imageView.imageProperty().unbind();
if (empty || item == null || item.getText() == null) {
setText(null);
setGraphic(null);
styleProperty().set(null);
} else {
setText(item.getText());
setGraphic(imageView);
imageView.imageProperty().bind(
Bindings.when(
item.readProperty()
).then(readImage)
.otherwise(unreadImage)
);
styleProperty().bind(
Bindings.when(
item.readProperty()
).then("-fx-background-color: red;")
.otherwise("-fx-background-color: null;")
);
}
}
};
An alternate (and possibly preferable) way to handle this from above is to instead get the style class or psuedoclass of the cell and update that based upon the boolean property. Then define the style in a separate CSS stylesheet. The output of the sample below is the same as the graphic based sample above.
mail.css
.readable:read {
-fx-background-color: red;
-fx-graphic: url(
"http://icons.iconarchive.com/icons/icons8/ios7/16/Messaging-Read-Message-icon.png"
);
}
.readable:unread {
-fx-graphic: url(
"http://icons.iconarchive.com/icons/oxygen-icons.org/oxygen/16/Status-mail-unread-new-icon.png"
);
}
Pseudo-class based code snippet:
PseudoClass READ_PSEUDO_CLASS = PseudoClass.getPseudoClass("read");
PseudoClass UNREAD_PSEUDO_CLASS = PseudoClass.getPseudoClass("unread");
tree.setCellFactory(new Callback<TreeView<Message>, TreeCell<Message>>() {
#Override
public TreeCell<Message> call(TreeView<Message> param) {
return new TreeCell<Message>() {
private ChangeListener<Boolean> readChangeListener = (observable, oldValue, newValue) -> {
pseudoClassStateChanged(READ_PSEUDO_CLASS, newValue);
pseudoClassStateChanged(UNREAD_PSEUDO_CLASS, !newValue);
};
Message priorItem = null;
{
getStyleClass().add("readable");
}
#Override
protected void updateItem(Message item, boolean empty) {
super.updateItem(item, empty);
if (priorItem != null) {
priorItem.readProperty().removeListener(readChangeListener);
}
priorItem = item;
if (empty || item == null || item.getText() == null) {
setText(null);
pseudoClassStateChanged(READ_PSEUDO_CLASS, false);
pseudoClassStateChanged(UNREAD_PSEUDO_CLASS, false);
} else {
item.readProperty().addListener(readChangeListener);
setText(item.getText());
pseudoClassStateChanged(READ_PSEUDO_CLASS, item.isRead());
pseudoClassStateChanged(UNREAD_PSEUDO_CLASS, !item.isRead());
}
}
};
}
});
I have been trying to accomplish editing of a cell in TableView which has long text on it. for that I have to pass the value to an external window TextArea and return the result to the field, but I can not get the value changed but after pushing ENTER and then select a new row with the mouse. The keyboard gets frozen. If I click same row the field doe snot change
My code is
contact_view.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue obs, Object oldSelection, Object newSelection) {
if (newSelection != null) {
TableView.TableViewSelectionModel selectionModel = contact_view.getSelectionModel();
ObservableList selectedCells = selectionModel.getSelectedCells();
tablePosition = (TablePosition) selectedCells.get(0);
row = tablePosition.getRow();
col = tablePosition.getColumn();
Object colname = tablePosition.getTableColumn().getId();
val = (String) notes.getCellData(row);
String tmp="";
tmp = String.valueOf(cntac_name.getCellData(row));
frozenCol.setText(tmp);
System.out.println("Hola mundo 563 "+" "+row +" "+col+" "+val+" "+colname+" "+tmp);
// frozenCol.setText((java.lang.String) val);
if(col == 21) {
notes.setCellFactory(TextFieldTableCell.<Contact>forTableColumn());
notes.setOnEditStart(new EventHandler<CellEditEvent<Contact, String>>() {
#Override
public void handle(CellEditEvent<Contact, String> t) {
((Contact) t.getTableView().getItems().get(t.getTablePosition().getRow())).setNotes(t.getNewValue());
String cscode =t.getTableView().getItems().get(t.getTablePosition().getRow()).cstmr_code;
String newValue =t.getNewValue();
// notes.setText((java.lang.String) newValue);
System.out.println("Hola mundo 615 "+t.getNewValue()+" "+t.getTableView().getItems().get(t.getTablePosition().getRow()).cstmr_code);
String ok=String.valueOf(cssEditorFld.getText());
System.out.println("Hola mundo 617 "+" "+row +" "+col+" "+val+" "+cntac_name.getCellData(row)+" "+cssEditorFld.getText()+" "+ok);
// boolean r = CstmersDBConnection.UpdateSingleContact(cscode, cntac_code, colname, newValue);
//setCellFactory(SetEditorTxtArea());
Stage s=new Stage();
Object p =cntac_name.getCellData(row);
s.initModality(Modality.APPLICATION_MODAL);
s.initStyle(StageStyle.DECORATED);
s.setResizable(true);
s.setTitle(String.valueOf(p));
Group root = new Group();
GridPane gridpane = new GridPane();
gridpane.setPadding(new Insets(1));
gridpane.setHgap(1);
gridpane.setVgap(1);
cssEditorFld.setText(String.valueOf(val));
cssEditorFld.setPrefRowCount(20);
cssEditorFld.setPrefColumnCount(150);
cssEditorFld.setWrapText(true);
cssEditorFld.setPrefWidth(300);
GridPane.setHalignment(cssEditorFld, HPos.CENTER);
gridpane.add(cssEditorFld, 0, 1);
HBox hbox = addHBox();
gridpane.add(hbox, 0, 2);
root.getChildren().add(gridpane);
s.setScene(new Scene(root));
s.show();
buttonYs.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
System.out.println(" Clicked Yes");
String ok=String.valueOf(cssEditorFld.getText());
n= true;
s.hide();
((Contact) t.getTableView().getItems().get(t.getTablePosition().getRow())).setNotes(cssEditorFld.getText());
System.out.println("Hola mundo 627 "+" "+row +" "+col+" "+cssEditorFld.getText()+" "+((Contact) t.getTableView().getItems().get(t.getTablePosition().getRow())).notes);
}
});
System.out.println("Hola mundo 631 "+" "+row +" "+col+" "+val+" "+p+" "+cntac_name.getCellData(row)+" "+cssEditorFld.getText());
buttonNo.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
System.out.println(" Clicked No");
n = false;
s.hide();
}
});
}
});
I would not use the TableView editing API if you intend to edit the value externally to the table (it's not really clear how you would do this, or what the TextFieldTableCell you use is for if you intend to edit the data elsewhere).
Just use a cell factory that opens the new window to perform the editing when this cell is double-clicked. You can pass a callback (represented, for example, as a Consumer) to the window for updating the value in the table model.
Here is a SSCCE. The "Item" column edits in the "usual" way using the built-in TextFieldTableCell: the "Description" column edits using an external window.
import java.util.function.Consumer;
import java.util.function.Function;
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextArea;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
public class ExternalEditingTable extends Application {
private void showEditingWindow(Window owner, String currentValue, Consumer<String> commitHandler) {
Stage stage = new Stage();
stage.initOwner(owner);
stage.initModality(Modality.APPLICATION_MODAL);
TextArea textArea = new TextArea(currentValue);
Button okButton = new Button("OK");
okButton.setDefaultButton(true);
okButton.setOnAction(e -> {
commitHandler.accept(textArea.getText());
stage.hide();
});
Button cancelButton = new Button("Cancel");
cancelButton.setCancelButton(true);
cancelButton.setOnAction(e -> stage.hide());
HBox buttons = new HBox(5, okButton, cancelButton);
buttons.setAlignment(Pos.CENTER);
buttons.setPadding(new Insets(5));
BorderPane root = new BorderPane(textArea, null, null, buttons, null);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
TableColumn<Item, String> nameCol = column("Item", Item::nameProperty);
// just use standard editing approach as data is "small":
nameCol.setCellFactory(TextFieldTableCell.forTableColumn());
TableColumn<Item, String> descriptionCol = column("Description", Item::descriptionProperty);
// set up this column to show an editing window when double-clicked:
descriptionCol.setCellFactory(tc -> {
TableCell<Item, String> cell = new TableCell<Item, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : item);
}
};
cell.setOnMouseClicked(e -> {
if (e.getClickCount() == 2 && ! cell.isEmpty()) {
showEditingWindow(table.getScene().getWindow(), cell.getItem(), newValue -> {
Item item = table.getItems().get(cell.getIndex());
item.setDescription(newValue);
});
}
});
return cell ;
});
table.getColumns().add(nameCol);
table.getColumns().add(descriptionCol);
for (int i = 1 ; i <= 20 ; i++) {
table.getItems().add(new Item("Item "+i, "This is a long and\nwordy description of item "+i));
}
Scene scene = new Scene(table, 600, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private static <S,T> TableColumn<S,T> column(String title, Function<S, Property<T>> prop) {
TableColumn<S,T> col = new TableColumn<>(title);
col.setCellValueFactory(cellData -> prop.apply(cellData.getValue()));
return col ;
}
public static class Item {
private final StringProperty description = new SimpleStringProperty() ;
private final StringProperty name = new SimpleStringProperty() ;
public Item(String name, String description) {
setName(name);
setDescription(description);
}
public final StringProperty descriptionProperty() {
return this.description;
}
public final String getDescription() {
return this.descriptionProperty().get();
}
public final void setDescription(final String description) {
this.descriptionProperty().set(description);
}
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 static void main(String[] args) {
launch(args);
}
}
Setting the cellFactory in a listener to the selection model is a bad practice. Rather than doing it this way, create a custom TableCell in the cellFactory for the column customize the editing by overwriting commitEdit, cancelEdit and startEdit.
Example:
TableView<StringProperty> tv = new TableView(FXCollections.observableArrayList(
new SimpleStringProperty("ajds bnas röu bngöspui rgöin röeisnpnhn reishn reinnb ökb rhkbhkbn afeae"),
new SimpleStringProperty("aap nja öaw njeaj nanja nja njja n"))
);
TableColumn<StringProperty, String> col = new TableColumn<>();
tv.getColumns().add(col);
tv.setEditable(true);
col.setCellValueFactory(cd -> cd.getValue());
col.setCellFactory(column -> new TableCell<StringProperty, String>() {
private final Text text;
private Stage editingStage;
{
text = new Text();
text.wrappingWidthProperty().bind(column.widthProperty());
setGraphic(text);
}
#Override
public void cancelEdit() {
super.cancelEdit();
closeStage();
}
private void closeStage() {
if (editingStage != null && editingStage.isShowing()) {
editingStage.setOnHidden(null);
editingStage.hide();
editingStage = null;
}
}
#Override
public void commitEdit(String newValue) {
super.commitEdit(newValue);
closeStage();
}
#Override
public void startEdit() {
super.startEdit();
// create editing ui
Button cancel = new Button("Cancel");
cancel.setCancelButton(true);
cancel.setOnAction(evt -> cancelEdit());
TextArea editor = new TextArea(getItem());
Button commit = new Button("OK");
commit.setOnAction(evt -> commitEdit(editor.getText()));
VBox vbox = new VBox(10, editor, commit, cancel);
// display editing window
Scene scene = new Scene(vbox);
editingStage = new Stage();
editingStage.initModality(Modality.WINDOW_MODAL);
editingStage.initOwner(getScene().getWindow());
editingStage.setScene(scene);
editingStage.setOnHidden(evt -> this.cancelEdit());
editingStage.show();
}
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
cancelEdit();
text.setText(item);
}
});
I am trying to use the table view to render / edit the "key = value" pairs.
So the table is supposed to have two columns : "key" and "value". Key is just a normal string, and value can be anything. My problem is that the data type of the values can be different from row to row. Basically, I wanted to use checkboxes for boolean values and choices for lists. I have found a way to render the whole table column with checkboxes or choices by setting the cell factory:
final TableColumn<FieldValue, Field> valueColumn = new TableColumn<>("Value");
valueColumn.setCellFactory(new Callback<TableColumn<FieldValue, Field>, TableCell<FieldValue, Field>>() {
#Override
public TableCell<FieldValue, Field> call(final TableColumn<FieldValue, Field> column) {
// if (value instanceof Boolean)
return new CheckBoxTableCell<>();
}
});
But what I need is to be able to insert a condition based on the type of the item that is going to be rendered inside the cell. In other words, some cell factory on the cell level and not on the column level. And that evaluates my condition at the render time. I haven't found still any solution to that. Maybe someone has some proper techniques for implementing this sort of rendering? Maybe some 3rd party datagrid?
Here is a table displaying pairs of Strings and Objects of various types.
A custom cell factory is used to handle display of different object types (by performing an instanceof check on the object's type and rendering the appropriate text or graphic).
import javafx.application.*;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ObservableValue;
import javafx.collections.*;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.Pair;
public class PairTable extends Application {
public static final String NAME_COLUMN_NAME = "Name";
public static final String VALUE_COLUMN_NAME = "Value";
final TableView<Pair<String, Object>> table = new TableView<>();
public static void main(String[] args) throws Exception {
launch(args);
}
public void start(final Stage stage) throws Exception {
// model data
ObservableList<Pair<String, Object>> data = FXCollections.observableArrayList(
pair("Song", "Bach Cello Suite 2"),
pair("Image", new Image("http://upload.wikimedia.org/wikipedia/en/9/99/Bach_Seal.jpg")),
pair("Rating", 4),
pair("Classic", true),
pair("Song Data", new byte[]{})
);
table.getItems().setAll(data);
table.setPrefHeight(275);
// table definition
TableColumn<Pair<String, Object>, String> nameColumn = new TableColumn<>(NAME_COLUMN_NAME);
nameColumn.setPrefWidth(100);
TableColumn<Pair<String, Object>, Object> valueColumn = new TableColumn<>(VALUE_COLUMN_NAME);
valueColumn.setSortable(false);
valueColumn.setPrefWidth(150);
nameColumn.setCellValueFactory(new PairKeyFactory());
valueColumn.setCellValueFactory(new PairValueFactory());
table.getColumns().setAll(nameColumn, valueColumn);
valueColumn.setCellFactory(new Callback<TableColumn<Pair<String, Object>, Object>, TableCell<Pair<String, Object>, Object>>() {
#Override
public TableCell<Pair<String, Object>, Object> call(TableColumn<Pair<String, Object>, Object> column) {
return new PairValueCell();
}
});
// layout the scene.
final StackPane layout = new StackPane();
layout.getChildren().setAll(table);
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
private Pair<String, Object> pair(String name, Object value) {
return new Pair<>(name, value);
}
}
class PairKeyFactory implements Callback<TableColumn.CellDataFeatures<Pair<String, Object>, String>, ObservableValue<String>> {
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Pair<String, Object>, String> data) {
return new ReadOnlyObjectWrapper<>(data.getValue().getKey());
}
}
class PairValueFactory implements Callback<TableColumn.CellDataFeatures<Pair<String, Object>, Object>, ObservableValue<Object>> {
#SuppressWarnings("unchecked")
#Override
public ObservableValue<Object> call(TableColumn.CellDataFeatures<Pair<String, Object>, Object> data) {
Object value = data.getValue().getValue();
return (value instanceof ObservableValue)
? (ObservableValue) value
: new ReadOnlyObjectWrapper<>(value);
}
}
class PairValueCell extends TableCell<Pair<String, Object>, Object> {
#Override
protected void updateItem(Object item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
if (item instanceof String) {
setText((String) item);
setGraphic(null);
} else if (item instanceof Integer) {
setText(Integer.toString((Integer) item));
setGraphic(null);
} else if (item instanceof Boolean) {
CheckBox checkBox = new CheckBox();
checkBox.setSelected((boolean) item);
setGraphic(checkBox);
} else if (item instanceof Image) {
setText(null);
ImageView imageView = new ImageView((Image) item);
imageView.setFitWidth(100);
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
setGraphic(imageView);
} else {
setText("N/A");
setGraphic(null);
}
} else {
setText(null);
setGraphic(null);
}
}
}