I am trying to implement a TreeTableView in javafx where the first column holds string values and the third one is to be rendered as 3-state checkboxes.
With the following MCVE I am able to get a treetable but none of the selections in the checkboxes persist on parent collapse/expand or on resize of the table.
MCVE
Class A is parent.
Class B extends A and is child.
Class C represents the 2nd column, (rendered as checkboxes)
Class MVCECheckBox builds the treetable and displays it.
A.java
package mcve.checkbox;
import java.util.List;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* #author returncode13
*/
public class A {
StringProperty name=new SimpleStringProperty();
C check=new C();
List<A> children;
public StringProperty getName() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public C getCheck() {
return check;
}
public void setCheck(C check) {
this.check = check;
}
public StringProperty nameProperty(){
return name;
}
public List<A> getChildren() {
return children;
}
public void setChildren(List<A> children) {
this.children = children;
}
}
B.java
package mcve.checkbox;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* #author returncode13
*/
public class B extends A{
StringProperty name=new SimpleStringProperty();
C Check=new C();
#Override
public StringProperty getName() {
return name;
}
#Override
public void setName(String name) {
this.name.set(name);
}
#Override
public C getCheck() {
return Check;
}
#Override
public void setCheck(C Check) {
this.Check = Check;
}
#Override
public StringProperty nameProperty(){
return name;
}
}
C.java
package mcve.checkbox;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
/**
*
* #author returncode13
*/
public class C {
BooleanProperty checkUncheck=new SimpleBooleanProperty();
BooleanProperty indeterminate=new SimpleBooleanProperty();
public BooleanProperty getCheckUncheck() {
return checkUncheck;
}
public void setCheckUncheck(BooleanProperty checkUncheck) {
this.checkUncheck = checkUncheck;
}
public BooleanProperty getIndeterminate() {
return indeterminate;
}
public void setIndeterminate(BooleanProperty indeterminate) {
this.indeterminate = indeterminate;
}
public BooleanProperty checkUncheckProperty(){
return checkUncheck;
}
public BooleanProperty indeterminateProperty(){
return indeterminate;
}
}
MCVECheckBox.java
package mcve.checkbox;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
*
* #author returncode13
*/
public class MCVECheckBox extends Application {
A selectedItem;
private TreeTableView<A> treetable=new TreeTableView<>();
#Override
public void start(Stage primaryStage) {
//setting up parents (A) and children (B)
A a1=new A();
a1.setName("A1");
List<A> A1Children=new ArrayList();
B b11=new B();
b11.setName("B11");
B b12=new B();
b12.setName("B12");
A1Children.add(b11);
A1Children.add(b12);
a1.setChildren(A1Children);
A a2=new A();
a2.setName("A2");
List<A> A2Children=new ArrayList();
B b21=new B();
b21.setName("B21");
B b22=new B();
b22.setName("B22");
A2Children.add(b21);
A2Children.add(b22);
a2.setChildren(A2Children);
//tree columns . first one holds strings
TreeTableColumn<A,String> name=new TreeTableColumn<>("Name");
name.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
//2nd tree columns. rendered as checkboxes. boolean values
TreeTableColumn<A,Boolean> checks=new TreeTableColumn<>("Checks");
checks.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<A, Boolean>, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(TreeTableColumn.CellDataFeatures<A, Boolean> param) {
A a=param.getValue().getValue();
SimpleBooleanProperty checkUncheck=new SimpleBooleanProperty();
SimpleBooleanProperty indeterminate=new SimpleBooleanProperty();
checkUncheck=(SimpleBooleanProperty) a.getCheck().getCheckUncheck();
indeterminate=(SimpleBooleanProperty) a.getCheck().getIndeterminate();
//to do: set parents status based on children status.
if(indeterminate.get()){
return indeterminate;
}else{
return checkUncheck;
}
}
});
checks.setCellFactory(new Callback<TreeTableColumn<A, Boolean>, TreeTableCell<A, Boolean>>() {
#Override
public TreeTableCell<A, Boolean> call(TreeTableColumn<A, Boolean> param) {
return new CheckBoxCell();
}
});
//building the tree;
TreeItem<A> a1item=new TreeItem<>(a1);
TreeItem<A> b11item=new TreeItem<>(b11);
TreeItem<A> b12item=new TreeItem<>(b12);
a1item.getChildren().add(b11item);
a1item.getChildren().add(b12item);
TreeItem<A> a2item=new TreeItem<>(a2);
TreeItem<A> b21item=new TreeItem<>(b21);
TreeItem<A> b22item=new TreeItem<>(b22);
a2item.getChildren().add(b21item);
a2item.getChildren().add(b22item);
TreeItem<A> root=new TreeItem<>();
root.getChildren().add(a1item);
root.getChildren().add(a2item);
treetable.getColumns().addAll(name,checks);
treetable.setRoot(root);
treetable.setShowRoot(false);
treetable.setEditable(true);
// StackPane rootSp = new StackPane();
Scene scene = new Scene(treetable, 300, 250);
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
//to render checkboxes in treetable
private class CheckBoxCell extends TreeTableCell<A, Boolean> {
CheckBox checkbox;
public CheckBoxCell() {
checkbox=new CheckBox();
checkbox.setAllowIndeterminate(true);
checkbox.selectedProperty().addListener((obs,wasSelected,isNowSelected) -> {
if(isNowSelected){
selectedItem=getTreeTableRow().getItem();
}
});
}
#Override
public void updateItem(Boolean b,boolean empty){
super.updateItem(b, empty);
if(empty){
setGraphic(null);
}else{
checkbox.setSelected(b);
setGraphic(checkbox);
}
}
}
}
I have earlier tried using the CheckTreeTableCell to set the cell factory on the second column, but soon found out that the CheckTreeTableCell doesn't support 3-state (check,uncheck,indeterminate) checkboxes.
After which I tried implementing the above code. Although I am able to bring in 3-state checkboxes, I am unable to let their state persist. Each time a parent is collapsed/expanded the checks made on its children become unselected.
Thanks for any help on determining a fix.
I am now able to implement the 3-state checkbox with the following modifications to the posted MCVE which is now a complete working example .
A.java (parent class)
package com.mycompany.yetanothercheckbox;
import java.util.Iterator;
import java.util.List;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* #author returncode13
*/
public class A {
private StringProperty name=new SimpleStringProperty();
C check=new C();
List<A> children;
final boolean isLeaf=false;
final boolean isParent=true;
public boolean updateParent=false;
public boolean updateChildren=false;
public boolean isLeaf() {
return isLeaf;
}
public boolean isParent() {
return isParent;
}
public StringProperty getName() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
public C getCheck() {
return check;
}
public void setCheck(C check) {
this.check = check;
for (Iterator<A> iterator = children.iterator(); iterator.hasNext();) {
A next = iterator.next();
next.setCheck(check);
}
}
public StringProperty nameProperty(){
return name;
}
public List<A> getChildren() {
return children;
}
public void setChildren(List<A> children) {
this.children = children;
}
public A getParent() {
return this;
}
}
B.java (child class)
package com.mycompany.yetanothercheckbox;
import java.util.List;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
*
* #author returncode13
*/
public class B extends A{
private StringProperty name=new SimpleStringProperty();
C Check=new C();
final boolean isLeaf=true;
final boolean isParent=false;
public boolean updateParent=false;
public boolean updateChildren=false;
A parent;
public A getParent() {
return parent;
}
public void setParent(A parent) {
this.parent = parent;
}
#Override
public boolean isLeaf() {
return isLeaf;
}
#Override
public boolean isParent() {
return isParent;
}
#Override
public StringProperty getName() {
return name;
}
#Override
public void setName(String name) {
this.name.set(name);
}
#Override
public C getCheck() {
return Check;
}
#Override
public void setCheck(C Check) {
this.Check = Check;
}
#Override
public StringProperty nameProperty(){
return name;
}
#Override
public List<A> getChildren() {
return parent.getChildren();
}
}
C.java (Hold Check states)
package com.mycompany.yetanothercheckbox;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
/**
*
* #author returncode13
*/
public class C {
BooleanProperty checkUncheck=new SimpleBooleanProperty();
BooleanProperty indeterminate=new SimpleBooleanProperty();
public BooleanProperty getCheckUncheck() {
return checkUncheck;
}
public void setCheckUncheck(BooleanProperty checkUncheck) {
this.checkUncheck = checkUncheck;
}
public BooleanProperty getIndeterminate() {
return indeterminate;
}
public void setIndeterminate(BooleanProperty indeterminate) {
this.indeterminate = indeterminate;
}
public BooleanProperty checkUncheckProperty(){
return checkUncheck;
}
public BooleanProperty indeterminateProperty(){
return indeterminate;
}
}
ThreeStateCheckBoxTreeTableCell.java (the 3state checkbox for tree table)
package com.mycompany.yetanothercheckbox;
import java.util.List;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.input.MouseEvent;
/**
*
* #author returncode13
*/
//to render checkboxes in treetable
public class ThreeStateCheckBoxTreeTableCell extends TreeTableCell<A, Boolean> {
A selectedItem;
CheckBox checkbox;
TreeTableColumn<A,Boolean> param;
/*private static boolean updateParent=false;
private static boolean updateChildren=false;*/
public ThreeStateCheckBoxTreeTableCell(TreeTableColumn<A,Boolean> param) {
checkbox=new CheckBox();
this.param=param;
checkbox.setAllowIndeterminate(true);
checkbox.selectedProperty().addListener((obs,wasSelected,isNowSelected) -> {
int sel=getTreeTableRow().getIndex();
selectedItem=this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
selectedItem.getCheck().getCheckUncheck().set(isNowSelected);
selectedItem.getCheck().getIndeterminate().set(false);
//ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
});
checkbox.indeterminateProperty().addListener((obx,ol,newV)->{
int sel=getTreeTableRow().getIndex();
selectedItem=this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
selectedItem.getCheck().getIndeterminate().set(newV);
//ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
});
checkbox.setOnMouseClicked(new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent event) {
int sel=getTreeTableRow().getIndex();
selectedItem=ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
if(selectedItem.isParent()){
selectedItem.updateChildren=true;
for(A child:selectedItem.getChildren()){
child.updateParent=false;
}
updateDownwards();
}
if(selectedItem.isLeaf()){
selectedItem.getParent().updateChildren=false;
selectedItem.updateParent=true;
updateUpWards();
}
ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
}
});
}
#Override
public void updateItem(Boolean b,boolean empty){
super.updateItem(b, empty);
if(empty){
setGraphic(null);
}else{
if(b==null){
checkbox.setIndeterminate(true);
}
else{
checkbox.setIndeterminate(false);
checkbox.setSelected(b);
}
setGraphic(checkbox);
}
ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
}
private void updateUpWards(){
if(selectedItem.updateParent){
List<A> children=selectedItem.getChildren();
int indeterminateCount=0;
int selectedCount=0;
A parent=selectedItem.getParent();
for(A child:children){
indeterminateCount+=child.getCheck().getIndeterminate().get()?1:0;
selectedCount+=child.getCheck().getCheckUncheck().get()?1:0;
}
if(indeterminateCount>0) {
parent.getCheck().getIndeterminate().set(true);
}
else if(indeterminateCount==0 && selectedCount==children.size()){
parent.getCheck().getIndeterminate().set(false);
parent.getCheck().getCheckUncheck().set(true);
}else{
parent.getCheck().getIndeterminate().set(false);
parent.getCheck().getCheckUncheck().set(false);
}
}
ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
}
private void updateDownwards(){
List<A> children=selectedItem.getChildren();
if(selectedItem.isParent() && selectedItem.updateChildren ){
for(A child:children){
child.getCheck().getCheckUncheck().set(selectedItem.getCheck().getCheckUncheck().get());
child.getCheck().getIndeterminate().set(selectedItem.getCheck().getIndeterminate().get());
}
}
ThreeStateCheckBoxTreeTableCell.this.param.getTreeTableView().refresh();
}
}
MainApp.java (Application as a POC)
package com.mycompany.yetanothercheckbox;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.stage.Stage;
import javafx.util.Callback;
public class MainApp extends Application {
private TreeTableView<A> treetable=new TreeTableView<>();
#Override
public void start(Stage primaryStage) {
//setting up parents (A) and children (B)
A a1=new A();
a1.setName("A1");
List<A> A1Children=new ArrayList();
B b11=new B();
b11.setName("B11");
B b12=new B();
b12.setName("B12");
b11.setParent(a1);
b12.setParent(a1);
A1Children.add(b11);
A1Children.add(b12);
a1.setChildren(A1Children);
A a2=new A();
a2.setName("A2");
List<A> A2Children=new ArrayList();
B b21=new B();
b21.setName("B21");
B b22=new B();
b22.setName("B22");
b21.setParent(a2);
b22.setParent(a2);
A2Children.add(b21);
A2Children.add(b22);
a2.setChildren(A2Children);
//tree columns . first one holds strings
TreeTableColumn<A,String> name=new TreeTableColumn<>("Name");
name.setCellValueFactory(new TreeItemPropertyValueFactory<>("name"));
//2nd tree columns. rendered as checkboxes. boolean values
TreeTableColumn<A,Boolean> checks=new TreeTableColumn<>("Checks");
checks.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<A, Boolean>, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(TreeTableColumn.CellDataFeatures<A, Boolean> param) {
A a=param.getValue().getValue();
SimpleBooleanProperty checkUncheck=new SimpleBooleanProperty();
SimpleBooleanProperty indeterminate=new SimpleBooleanProperty();
checkUncheck.bindBidirectional(a.getCheck().getCheckUncheck());
indeterminate.bindBidirectional(a.getCheck().getIndeterminate());
checkUncheck.addListener(new ChangeListener<Boolean>(){
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
a.getCheck().indeterminateProperty().set(false);
a.getCheck().checkUncheckProperty().set(newValue);
}
});
indeterminate.addListener(new ChangeListener<Boolean>(){
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
a.getCheck().indeterminateProperty().set(newValue);
}
});
if(indeterminate.get()){
return null;
}else{
return checkUncheck;
}
}
});
checks.setCellFactory(new Callback<TreeTableColumn<A, Boolean>, TreeTableCell<A, Boolean>>() {
#Override
public TreeTableCell<A, Boolean> call(TreeTableColumn<A, Boolean> param) {
return new ThreeStateCheckBoxTreeTableCell(param);
}
});
//building the tree;
TreeItem<A> a1item=new TreeItem<>(a1);
TreeItem<A> b11item=new TreeItem<>(b11);
TreeItem<A> b12item=new TreeItem<>(b12);
a1item.getChildren().add(b11item);
a1item.getChildren().add(b12item);
TreeItem<A> a2item=new TreeItem<>(a2);
TreeItem<A> b21item=new TreeItem<>(b21);
TreeItem<A> b22item=new TreeItem<>(b22);
a2item.getChildren().add(b21item);
a2item.getChildren().add(b22item);
TreeItem<A> root=new TreeItem<>();
root.getChildren().add(a1item);
root.getChildren().add(a2item);
treetable.getColumns().addAll(name,checks);
treetable.setRoot(root);
treetable.setShowRoot(false);
treetable.setEditable(true);
// StackPane rootSp = new StackPane();
Scene scene = new Scene(treetable, 300, 250);
primaryStage.setTitle("CheckBoxTreeTable Example");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Thank you very much for this great piece of code that gives insight on the fact that the 3-state checkbox does NOT exist for a tree item table cell (
For those who will feed on this post, I made it work with some minor changes on the class ''. Now looking like this :
package com.mycompany.yetanothercheckbox;
import java.util.List;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
/**
*
* #author returncode13
*/
// to render checkboxes in treetable
public class ThreeStateCheckBoxTreeTableCell extends TreeTableCell<A, Boolean> {
A selectedItem;
CheckBox checkbox;
TreeTableColumn<A, Boolean> param;
/*
* private static boolean updateParent=false;
* private static boolean updateChildren=false;
*/
public ThreeStateCheckBoxTreeTableCell(TreeTableColumn<A, Boolean> param) {
checkbox = new CheckBox();
this.param = param;
checkbox.setAllowIndeterminate(true);
checkbox.selectedProperty().addListener((obs,
oldSelectedVal,
newSelectedVal) -> {
int sel = getTreeTableRow().getIndex();
selectedItem = this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
selectedItem.getCheck().getCheckUncheck().set(newSelectedVal);
selectedItem.getCheck().getIndeterminate().set(false);
});
checkbox.indeterminateProperty().addListener((obx,
ol,
newV) -> {
int sel = getTreeTableRow().getIndex();
selectedItem = this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
selectedItem.getCheck().getIndeterminate().set(newV);
});
checkbox.setOnMouseClicked(event -> {
int sel = getTreeTableRow().getIndex();
selectedItem = this.param.getTreeTableView().getSelectionModel().getModelItem(sel).getValue();
if (selectedItem.isParent()) {
selectedItem.updateChildren = true;
for (A child : selectedItem.getChildren()) { child.updateParent = false; }
updateDownwards();
}
if (selectedItem.isLeaf()) {
selectedItem.getParent().updateChildren = false;
selectedItem.updateParent = true;
updateUpWards();
}
});
}
#Override
public void updateItem(Boolean b,
boolean empty) {
super.updateItem(b, empty);
if (empty) {
setGraphic(null);
} else {
if (b == null) {
checkbox.setIndeterminate(true);
} else {
checkbox.setIndeterminate(false);
checkbox.setSelected(b);
}
setGraphic(checkbox);
}
}
private void updateUpWards() {
if (selectedItem.updateParent) {
List<A> children = selectedItem.getChildren();
int indeterminateCount = 0;
int selectedCount = 0;
A parent = selectedItem.getParent();
for (A child : children) {
indeterminateCount += child.getCheck().getIndeterminate().get() ? 1 : 0;
selectedCount += child.getCheck().getCheckUncheck().get() ? 1 : 0;
}
if (indeterminateCount > 0) {
parent.getCheck().getIndeterminate().set(true);
} else if (indeterminateCount == 0
&& selectedCount == children.size()) {
parent.getCheck().getIndeterminate().set(false);
parent.getCheck().getCheckUncheck().set(true);
} else {
parent.getCheck().getIndeterminate().set(false);
parent.getCheck().getCheckUncheck().set(false);
}
}
this.param.getTreeTableView().refresh();
}
private void updateDownwards() {
List<A> children = selectedItem.getChildren();
if (selectedItem.isParent()
&& selectedItem.updateChildren) {
for (A child : children) {
child.getCheck().getCheckUncheck().set(selectedItem.getCheck().getCheckUncheck().get());
child.getCheck().getIndeterminate().set(selectedItem.getCheck().getIndeterminate().get());
}
}
this.param.getTreeTableView().refresh();
}
}
Related
I have a TableColumn:
TableColumn<Foo, String> colStatus = new TableColumn("Status");
colStatus.setCellValueFactory(new PropertyValueFactory<>("statusElement"));
On this table I want to apply this cellFactory TextFieldTableCell.forTableColumn() which will make cell editable.
But I also want to combine this one with a custom cellFactory:
colStatus.setCellFactory(new Callback<>() {
public TableCell<Foo, String> call(TableColumn param) {
return new TableCell<>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (!isEmpty()){
if(item.equals("error")){
this.setTextFill(Color.RED);
setText(item);
}else{
this.setTextFill(Color.Black);
setText(item);
}
}
}
};
}
});
This cell factory sets text color of cell, based by cell value.
But I don't know how to make the cell editable but also to customize his color, based on value.
Here is MCVE:
#Override
public void start(Stage primaryStage){
List<String> test = new ArrayList<>();
test.add("done(green)");
test.add("done(green)");
test.add("fail(red)");
test.add("done(green)");
TableView<String> tableView = new TableView<>();
tableView.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
tableView.setEditable(true);
TableColumn<String, String> col = new TableColumn<>("Column");
col.setCellValueFactory(data -> new SimpleStringProperty(data.getValue()));
col.setCellFactory(TextFieldTableCell.forTableColumn());
//I want to apply a color based by value from cell
tableView.getColumns().add(col);
tableView.setItems(FXCollections.observableArrayList(test));
primaryStage.setScene(new Scene(tableView));
primaryStage.show();
}
I finally found I can use TextFieldTableCell.forTableColumn() with customization.
Just need to override TextFieldTableCell.
private static class CustomCell extends TextFieldTableCell<String, String>{
#Override
public void updateItem(String item, boolean empty){
super.updateItem(item, empty);
if(item == null || empty) {
setText(null);
return;
}
if(!isEmpty()){
if(item.equals("error")){
this.setTextFill(Color.RED);
setText(item);
}else{
this.setTextFill(Color.BLACK);
setText(item);
}
}
}
}
I was fixed on idea that setCellFactory get as parameter a CallBack.
So I was tried a lot of ways to get a CallBack which returns a TableCell
After I saw the answer of #Sedrick.
I found I can send an lambda implementation like: setCellFactory(e -> new CustomCell()).
Thanks to #Sedrick and #kleopatra.
The key is to extend TableCell as #fabian suggested.
Main
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
*
* #author blj0011
*/
public class JavaFXTestingGround extends Application
{
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception
{
List<String> test = new ArrayList<>();
test.add("done(green)");
test.add("done(green)");
test.add("fail(red)");
test.add("done(green)");
TableView<String> tableView = new TableView<>();
tableView.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
tableView.setEditable(true);
TableColumn<String, String> col = new TableColumn<>("Column");
col.setCellValueFactory(data -> new SimpleStringProperty(data.getValue()));
col.setCellFactory((param) -> new CustomCellFactory());
//I want to apply a color based by value from cell
tableView.getColumns().add(col);
tableView.setItems(FXCollections.observableArrayList(test));
primaryStage.setScene(new Scene(tableView));
primaryStage.show();
}
}
CustomCellFactory
import javafx.application.Platform;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
/**
*
* #author blj0011
*/
public class CustomCellFactory<T> extends TableCell<T, String>
{
TextField textField = new TextField();
Text text = new Text();
public CustomCellFactory()
{
textField.setOnKeyPressed(keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
commitEdit(textField.getText());
}
});
}
#Override
public void commitEdit(String newValue)
{
super.commitEdit(newValue);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
#Override
public void startEdit()
{
super.startEdit();
if (!isEmpty()) {
setGraphic(textField);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.setText(text.getText());
Platform.runLater(() -> textField.requestFocus());
}
}
#Override
public void cancelEdit()
{
super.cancelEdit();
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
#Override
protected void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
}
else {
if (item.equals("error")) {
text.setFill(Color.RED);
}
else {
text.setFill(Color.BLACK);
}
text.setText(item);
setGraphic(text);
}
}
}
Update: I found This while failing to live up to #kleopatra suggestion.
Main
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
import javafx.util.Callback;
/**
*
* #author blj0011
*/
public class JavaFXTestingGround extends Application
{
/**
* #param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception
{
List<String> test = new ArrayList<>();
test.add("done(green)");
test.add("done(green)");
test.add("fail(red)");
test.add("done(green)");
TableView<String> tableView = new TableView<>();
tableView.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY);
tableView.setEditable(true);
TableColumn<String, String> col = new TableColumn<>("Column");
col.setCellValueFactory(data -> new SimpleStringProperty(data.getValue()));
col.setCellFactory(column -> EditCell.createStringEditCell());
//I want to apply a color based by value from cell
tableView.getColumns().add(col);
tableView.setItems(FXCollections.observableArrayList(test));
primaryStage.setScene(new Scene(tableView));
primaryStage.show();
}
}
EditCell
import javafx.event.Event;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.util.StringConverter;
public class EditCell<S, T> extends TableCell<S, T>
{
// Text field for editing
// TODO: allow this to be a plugable control.
private final TextField textField = new TextField();
// Converter for converting the text in the text field to the user type, and vice-versa:
private final StringConverter<T> converter;
public EditCell(StringConverter<T> converter)
{
this.converter = converter;
itemProperty().addListener((obx, oldItem, newItem) -> {
if (newItem == null) {
setText(null);
}
else {
setText(converter.toString(newItem));
}
});
setGraphic(textField);
setContentDisplay(ContentDisplay.TEXT_ONLY);
textField.setOnAction(evt -> {
commitEdit(this.converter.fromString(textField.getText()));
});
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (!isNowFocused) {
commitEdit(this.converter.fromString(textField.getText()));
}
});
textField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (null != event.getCode()) {
switch (event.getCode()) {
case ESCAPE:
textField.setText(converter.toString(getItem()));
cancelEdit();
event.consume();
break;
case RIGHT:
getTableView().getSelectionModel().selectRightCell();
event.consume();
break;
case LEFT:
getTableView().getSelectionModel().selectLeftCell();
event.consume();
break;
case UP:
getTableView().getSelectionModel().selectAboveCell();
event.consume();
break;
case DOWN:
getTableView().getSelectionModel().selectBelowCell();
event.consume();
break;
default:
break;
}
}
});
}
/**
* Convenience converter that does nothing (converts Strings to themselves
* and vice-versa...).
*/
public static final StringConverter<String> IDENTITY_CONVERTER = new StringConverter<String>()
{
#Override
public String toString(String object)
{
return object;
}
#Override
public String fromString(String string)
{
return string;
}
};
/**
* Convenience method for creating an EditCell for a String value.
*
* #param <S>
* #return
*/
public static <S> EditCell<S, String> createStringEditCell()
{
return new EditCell<>(IDENTITY_CONVERTER);
}
// set the text of the text field and display the graphic
#Override
public void startEdit()
{
super.startEdit();
textField.setText(converter.toString(getItem()));
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
textField.requestFocus();
}
// revert to text display
#Override
public void cancelEdit()
{
super.cancelEdit();
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
// commits the edit. Update property if possible and revert to text display
#Override
public void commitEdit(T item)
{
// This block is necessary to support commit on losing focus, because the baked-in mechanism
// sets our editing state to false before we can intercept the loss of focus.
// The default commitEdit(...) method simply bails if we are not editing...
if (!isEditing() && !item.equals(getItem())) {
TableView<S> table = getTableView();
if (table != null) {
TableColumn<S, T> column = getTableColumn();
CellEditEvent<S, T> event = new CellEditEvent<>(table,
new TablePosition<>(table, getIndex(), column),
TableColumn.editCommitEvent(), item);
Event.fireEvent(column, event);
}
}
if (item.equals("error")) {
setTextFill(Color.RED);
}
else {
setTextFill(Color.BLACK);
}
super.commitEdit(item);
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
}
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.util.Callback;
import javafx.util.StringConverter;
class className {
private void initColumnTableValue() {
TableColumn<User, Number> columnId = new TableColumn<>("ID");
columnId.setCellFactory(getTextFieldCellFactoryId());
}
/*
* Returns the factory to a cell with a text field for editing
* but with the ability to customize cells
*/
private Callback<TableColumn<User, Number>, TableCell<User, Number>> getTextFieldCellFactoryId() {
return CustomTextFieldTableCell.<User, Number>forTableColumn(
new StringConverter<Number>() {
#Override
public String toString(Number number) {
return number == null ? "" : number.toString();
}
#Override
public Number fromString(String s) {
try {
return Integer.valueOf(s);
} catch (Exception e) {
return null;
}
}
}
);
}
}
/**
* Creates a changed factory for the production of editable cells,
* but with the ability to customize cells
*/
public class CustomTextFieldTableCell extends TextFieldTableCell<User, Number> {
public CustomTextFieldTableCell(StringConverter<Number> var0) {super(var0);}
{ setAlignment(Pos.TOP_CENTER); }
#Override
public void updateItem(Number value, boolean empty){
super.updateItem(value, empty);
if (value == null || empty)
setText("");
else {
if ((int)value % 2 == 0)
setBorder(new Border(new BorderStroke(Color.BLUE, BorderStrokeStyle.DASHED,
new CornerRadii(4), new BorderWidths(1))));
setText(value.toString());
}
}
public static <User, Number> Callback<TableColumn<User, Number>, TableCell<User, Number>> forTableColumn(StringConverter<Number> var0) {
return new Callback<TableColumn<User, Number>, TableCell<User, Number>>() {
#Override
public TableCell<User, Number> call(TableColumn<User, Number> var1) {
return (TableCell<User, Number>) new CustomTextFieldTableCell((StringConverter<java.lang.Number>) var0);
}
};
}
}
I've a JFXTreeTableView and i want to center the text of the data for each column.
there is one of my creating columns code :
JFXTreeTableColumn<TableData, String> DrinkColumn = new JFXTreeTableColumn<>("Drink");
DrinkColumn.setPrefWidth(100);
DrinkColumn.setCellValueFactory(new Callback<TreeTableColumn.CellDataFeatures<TableData, String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(TreeTableColumn.CellDataFeatures<TableData, String> param) {
return param.getValue().getValue().Drink;
}
}
);
I don't use JFoenix, but using a standard TreeTableView, the following external CSS will center the text in tree table cells:
.tree-table-cell {
-fx-alignment: center ;
}
Here's a SSCCE (the code above goes in style.css):
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.stage.Stage;
public class TreeTableViewTest extends Application {
#Override
public void start(Stage primaryStage) {
TreeTableView<Item> table = new TreeTableView<>();
TreeTableColumn<Item, String> col = new TreeTableColumn<>("Item");
col.setCellValueFactory(cellData -> cellData.getValue().getValue().nameProperty());
col.setPrefWidth(250);
table.getColumns().add(col);
TreeTableColumn<Item, Number> valueCol = new TreeTableColumn<>("Value");
valueCol.setCellValueFactory(cellData -> cellData.getValue().getValue().valueProperty());
valueCol.setPrefWidth(150);
table.getColumns().add(valueCol);
table.setRoot(createRandomTree(50));
Scene scene = new Scene(table);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
}
private TreeItem<Item> createRandomTree(int nItems) {
Random rng = new Random();
TreeItem<Item> root = new TreeItem<>(new Item("Item 1", rng.nextInt(1000)));
root.setExpanded(true);
List<TreeItem<Item>> items = new ArrayList<>();
items.add(root);
for (int i = 2 ; i <= nItems ; i++) {
TreeItem<Item> item = new TreeItem<>(new Item("Item "+i, rng.nextInt(1000)));
item.setExpanded(true);
items.get(rng.nextInt(items.size())).getChildren().add(item);
items.add(item);
}
return root ;
}
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 you want to center only specific columns, then use a cell factory on the column and set a CSS class or PseudoClass on the cell:
valueCol.setCellFactory(column -> {
TreeTableCell<Item, Number> cell = new TreeTableCell<Item, Number>() {
#Override
protected void updateItem(Number value, boolean empty) {
super.updateItem(value, empty);
if (empty) {
setText(null);
} else {
setText(value.toString());
}
}
};
cell.pseudoClassStateChanged(PseudoClass.getPseudoClass("centered"), true);
return cell ;
});
and modify the CSS accordingly:
.tree-table-cell:centered {
-fx-alignment: center ;
}
The latter version gives
I have Created a custom control call it ComboBoxTablePopup extending Comboboxbase class. I have used a tableview as a popup content. Everything works fine,
update value, show popup, hide popup. After switching the focus from ComboBoxTablePopup to another control like a TextField or Spinner, it updates it self with null value.
So, I don't know what makes this happens. So here is my implementation on self executable class.
import com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior;
import com.sun.javafx.scene.control.behavior.KeyBinding;
import com.sun.javafx.scene.control.skin.ComboBoxListViewSkin;
import com.sun.javafx.scene.control.skin.ComboBoxPopupControl;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.WeakListChangeListener;
import javafx.event.ActionEvent;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class TestComboboxTablePopup extends Application {
#Override
public void start(Stage primaryStage) throws Exception {
ComboBoxTablePopupControl<dataModel> comboBoxTablePopup = new ComboBoxTablePopupControl<>();
TableColumn<dataModel, Integer> tcId = new TableColumn<>("Id");
TableColumn<dataModel, String> tcName = new TableColumn<>("Name");
tcId.setCellValueFactory(new PropertyValueFactory<dataModel, Integer>("id"));
tcName.setCellValueFactory(new PropertyValueFactory<dataModel, String>("name"));
comboBoxTablePopup.setColumns(FXCollections.observableArrayList(tcId, tcName));
comboBoxTablePopup.setItems(FXCollections.observableArrayList(
new dataModel(1, "Data Model object 1"),
new dataModel(2, "Data Model object 2"),
new dataModel(3, "Data Model object 3")
));
VBox vBox = new VBox(comboBoxTablePopup);
Scene scene = new Scene(vBox);
primaryStage.setScene(scene);
primaryStage.setWidth(400);
primaryStage.setHeight(300);
primaryStage.show();
}
public class dataModel {
private int id;
private String name;
public dataModel(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
private static <S> StringConverter<S> defaultStringConverter() {
return new StringConverter<S>() {
#Override
public String toString(S t) {
return t == null ? "abood fait" : t.toString();
}
#Override
public S fromString(String string) {
return null;
}
};
}
private class ComboBoxTablePopupControl<S> extends ComboBoxBase {
/***************************************************************************
* *
* Static properties and methods *
* *
**************************************************************************/
private static final String DEFAULT_STYLE_CLASS = "combobox-table-popup";
private ObjectProperty<ObservableList<S>> items = new SimpleObjectProperty<ObservableList<S>>(this, "items");
public final void setItems(ObservableList<S> value) {
itemsProperty().set(value);
}
public final ObservableList<S> getItems() {
return items.get();
}
public ObjectProperty<ObservableList<S>> itemsProperty() {
return items;
}
public ObjectProperty<StringConverter<S>> converterProperty() {
return converter;
}
private ObjectProperty<StringConverter<S>> converter =
new SimpleObjectProperty<StringConverter<S>>(this, "converter", defaultStringConverter());
public final void setConverter(StringConverter<S> value) {
converterProperty().set(value);
}
public final StringConverter<S> getConverter() {
return converterProperty().get();
}
// Editor
private ReadOnlyObjectWrapper<TextField> editor;
public final TextField getEditor() {
return editorProperty().get();
}
public final ReadOnlyObjectProperty<TextField> editorProperty() {
if (editor == null) {
editor = new ReadOnlyObjectWrapper<TextField>(this, "editor");
editor.set(new ComboBoxListViewSkin.FakeFocusTextField());
}
return editor.getReadOnlyProperty();
}
private
ObservableList<TableColumn<S, ?>> columns = FXCollections.observableArrayList();
public ObservableList<TableColumn<S, ?>> getColumns() {
return columns;
}
public void setColumns(ObservableList<TableColumn<S, ?>> columns) {
this.columns = columns;
}
/***************************************************************************
* *
* Constructors *
* *
**************************************************************************/
/**
* Creates a default ComboboxTablePopup instance with an empty
* {#link #itemsProperty() items} list and default
* {#link #selectionModelProperty() selection model}.
*/
public ComboBoxTablePopupControl() {
this(FXCollections.<S>emptyObservableList());
}
/**
* Creates a default ComboboxTablePopup instance with the provided items list and
* a default { selection model}.
*/
public ComboBoxTablePopupControl(ObservableList<S> items) {
setItems(items);
getStyleClass().add(DEFAULT_STYLE_CLASS);
setEditable(true);
valueProperty().addListener((observable, oldValue, newValue) -> {
System.out.println(newValue);
});
}
public ComboBoxTablePopupControl(ObservableList<S> items, ObservableList<TableColumn<S, ?>> columns) {
this(items);
this.columns = columns;
}
#Override
protected Skin<?> createDefaultSkin() {
return new ComboBoxTablePopupControlSkin<>(this);
}
}
public class ComboBoxTablePopupControlSkin<S> extends ComboBoxPopupControl {
private ComboBoxTablePopupControl comboBoxTablePopup;
private ObservableList<S> comboboxTablePopupItems;
private TableView<S> tableViewPopupContent;
private ObservableList<S> tableViewPopupItems;
private Predicate<S> predicate;
private final InvalidationListener itemsObserver;
private final ListChangeListener<S> tableViewItemsListener = new ListChangeListener<S>() {
#Override
public void onChanged(ListChangeListener.Change<? extends S> c) {
getSkinnable().requestLayout();
}
};
private final WeakListChangeListener<S> weakListViewItemsListener =
new WeakListChangeListener<S>(tableViewItemsListener);
public ComboBoxTablePopupControlSkin(ComboBoxTablePopupControl comboBoxTablePopup) {
super(comboBoxTablePopup, new ComboBoxBaseBehavior(comboBoxTablePopup, null));
this.comboBoxTablePopup = comboBoxTablePopup;
updateComboBoxTablePopupItems();
itemsObserver = observable -> {
updateComboBoxTablePopupItems();
updateTableViewItems();
};
this.comboBoxTablePopup.itemsProperty().addListener(new WeakInvalidationListener(itemsObserver));
tableViewPopupContent = createTableView();
tableViewPopupContent.setManaged(false);
getChildren().add(tableViewPopupContent);
updateTableViewItems();
registerChangeListener(comboBoxTablePopup.converterProperty(), "CONVERTER");
registerChangeListener(comboBoxTablePopup.itemsProperty(), "ITEMS");
registerChangeListener(comboBoxTablePopup.valueProperty(), "VALUE");
registerChangeListener(comboBoxTablePopup.editorProperty(), "EDITABLE");
}
private void updateTableViewItems() {
this.tableViewPopupItems = comboBoxTablePopup.getItems();
this.tableViewPopupContent.setItems(this.tableViewPopupItems);
if (tableViewPopupItems != null) {
tableViewPopupItems.removeListener(weakListViewItemsListener);
}
this.tableViewPopupItems = comboboxTablePopupItems;
tableViewPopupContent.setItems(tableViewPopupItems);
if (tableViewPopupItems != null) {
tableViewPopupItems.addListener(weakListViewItemsListener);
}
getSkinnable().requestLayout();
}
public void updateComboBoxTablePopupItems() {
comboboxTablePopupItems = comboBoxTablePopup.getItems();
comboboxTablePopupItems = comboboxTablePopupItems == null ? FXCollections.<S>emptyObservableList() : comboboxTablePopupItems;
}
private TableView<S> createTableView() {
final TableView<S> tableView = new TableView<>();
tableView.setId("table-view");
tableView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
tableView.setFocusTraversable(false);
for (TableColumn tblColumn : tableColumns()) {
tableView.getColumns().add(tblColumn);
}
tableView.getSelectionModel().selectedItemProperty().addListener(o -> {
S selectedItem = tableView.getSelectionModel().getSelectedItem();
comboBoxTablePopup.setValue(selectedItem);
});
tableView.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.ENTER ||
e.getCode() == KeyCode.SPACE) {
S selectedItem = tableView.getSelectionModel().getSelectedItem();
comboBoxTablePopup.setValue(selectedItem);
comboBoxTablePopup.hide();
}
});
return tableView;
}
private ObservableList<TableColumn> tableColumns() {
return ((ComboBoxTablePopupControl) getSkinnable()).getColumns();
}
#Override
protected Node getPopupContent() {
return this.tableViewPopupContent;
}
#Override
protected TextField getEditor() {
return ((ComboBoxTablePopupControl) getSkinnable()).getEditor();
}
#Override
protected StringConverter<S> getConverter() {
return ((ComboBoxTablePopupControl) getSkinnable()).getConverter();
}
#Override
public Node getDisplayNode() {
Node displayNode;
displayNode = getEditableInputNode();
updateDisplayNode();
return displayNode;
}
#Override
protected void handleControlPropertyChanged(String p) {
if ("VALUE".equals(p)) {
updateDisplayNode();
System.out.println(comboBoxTablePopup.getValue());
comboBoxTablePopup.fireEvent(new ActionEvent());
} else if ("CONVERTER".equals(p)) {
updateDisplayNode();
System.out.println("Conveter peroptery");
} else if ("ITEMS".equals(p)) {
updateComboBoxTablePopupItems();
updateTableViewItems();
} else if ("EDITOR".equals(p)) {
getEditableInputNode();
} else
super.handleControlPropertyChanged(p);
}
}
}
If someone still has trouble with this issue, here is a workaround for a choicebox. I assume you already have a table with columns and an EventHandler. This example is not tested with any editable (isEditable(true)) object.
First set a custom Factory and an EventHandler for setOnEditStart
yourColumn.setCellFactory(value -> new CustomEditFactory(yourObsList));
In your CustomEditFactory class you have to extend TreeTableCell<KnotenObs, String> and #Override four methods
startEdit()
#Override
public void startEdit() {
super.startEdit();
choiceBox.getSelectionModel().select(getItem());
oldValue = choiceBox.getSelectionModel().getSelectedItem();
setGraphic(choiceBox);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
commitEdit()
#Override
public void commitEdit(String newValue) {
if(newValue == null) {
super.commitEdit(oldValue);
} else {
super.commitEdit(newValue);
}
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
cancelEdit()
#Override
public void cancelEdit() {
super.cancelEdit();
if(getItem() == null) {
setText(oldValue);
} else {
setText(getItem());
}
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
updateItem()
#Override
public void updateItem(String item, boolean empty) {
if(item == null) {
super.updateItem(oldValue, empty);
setText(oldValue);
} else {
super.updateItem(item, empty);
setText(item);
}
setContentDisplay(ContentDisplay.TEXT_ONLY);
}
I'm stuck with trying to format Long values in a TableView with JavaFX.
I have following class to store the rows that I want to display on the table:
import java.text.DecimalFormat;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleStringProperty;
public class DataByCurrencyPairRow {
private DecimalFormat integerFormat = new DecimalFormat("#,###");
private SimpleStringProperty currencyPair = new SimpleStringProperty("");
private SimpleDoubleProperty shareOfTotalVolume = new SimpleDoubleProperty(0);
private SimpleLongProperty totalVolume = new SimpleLongProperty(0);
private SimpleLongProperty currencyBought = new SimpleLongProperty(0);
private SimpleLongProperty currencySold = new SimpleLongProperty(0);
private SimpleLongProperty monthlyAverage = new SimpleLongProperty(0);
public DataByCurrencyPairRow() {
currencyPair.set("");
shareOfTotalVolume.set(0);
totalVolume.set(0);
currencyBought.set(0);
currencySold.set(0);
monthlyAverage.set(0);
}
public String getCurrencyPair() {
return currencyPair.getValue();
}
public void setCurrencyPair(String currencyPair) {
this.currencyPair.setValue(currencyPair);
}
public Long getMonthlyAverage() {
return monthlyAverage.getValue();
}
public void setMonthlyAverage(Long monthlyAverage) {
this.monthlyAverage.setValue(monthlyAverage);
}
public Long getCurrencySold() {
return currencySold.getValue();
}
public void setCurrencySold(Long currencySold) {
this.currencySold.setValue(currencySold);
}
public Long getCurrencyBought() {
return currencyBought.getValue();
}
public void setCurrencyBought(Long currencyBought) {
this.currencyBought.setValue(currencyBought);
}
public Long getTotalVolume() {
return totalVolume.getValue();
}
public void setTotalVolume(Long totalVolume) {
this.totalVolume.setValue(totalVolume);
}
public Double getShareOfTotalVolume() {
return shareOfTotalVolume.getValue();
}
public void setShareOfTotalVolume(Double shareOfTotalVolume) {
this.shareOfTotalVolume.setValue(shareOfTotalVolume);
}
}
Then I have the controller with initialize method where I have been trying to override the updateItem method to get the table to show comma as a thousand separator:
public class MainController {
private static final String DEFAULT_TIME_HORIZON = new String("0");
private final NumberFormat integerFormat = new DecimalFormat("#,###");
#FXML
TableView<DataByCurrencyPairRow> tableTransactionsByCurrencyPair;
#FXML
TableColumn<DataByCurrencyPairRow, Long> columnTotal;
#FXML
void initialize() {
columnTotal.setCellFactory(
new Callback<TableColumn<DataByCurrencyPairRow, SimpleLongProperty>, TableCell<DataByCurrencyPairRow, SimpleLongProperty>>() {
#Override
public TableCell<DataByCurrencyPairRow, SimpleLongProperty> call(TableColumn<DataByCurrencyPairRow, SimpleLongProperty> param
) {
return new TableCell<DataByCurrencyPairRow, SimpleLongProperty>() {
#Override
protected void updateItem(SimpleLongProperty item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText("0");
setStyle("");
} else {
setText(integerFormat.format(item.longValue()));
}
}
};
}
}
);
And this is the method that populates the TableView:
public void updateByCurrencyPairTable() {
System.out.println("#MainController: Updating data in table view Markets volumes by currency pair");
ObservableList<DataByCurrencyPairRow> data = tableTransactionsByCurrencyPair.getItems();
data.clear();
// Add row items to the table view Markets volume by currency
for (DataByCurrencyPairRow row : customer.getDataByCurrencyPairR12m().getDataByCurrencyPair()) {
data.add(row);
}
}
Please help me by showing how to do this!! I also tried to override the updateItem method as Long instead of SimpleLongProperty and my IDE accepted the code but still the number is not formatted in the table.
Thank you guys in advance!!!
LongProperty implements ObservableValue<Number>, not ObservableValue<Long> (or ObservableValue<SimpleLongProperty>). So your table columns need to be of type TableColumn<DataByCurrencyPair, Number> and your cell factory needs to match those types accordingly.
Here's a simple example of a formatted column with Longs:
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Random;
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
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 TableWithFormattedLong extends Application {
private final NumberFormat integerFormat = new DecimalFormat("#,###");
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
TableColumn<Item, String> itemColumn = new TableColumn<>("Item");
itemColumn.setCellValueFactory(cellData -> cellData.getValue().nameProperty());
TableColumn<Item, Number> valueColumn = new TableColumn<>("Value");
valueColumn.setCellValueFactory(cellData -> cellData.getValue().valueProperty());
valueColumn.setCellFactory(tc -> new TableCell<Item, Number>() {
#Override
protected void updateItem(Number value, boolean empty) {
super.updateItem(value, empty);
if (value == null || empty) {
setText("");
} else {
setText(integerFormat.format(value));
}
}
});
table.getColumns().add(itemColumn);
table.getColumns().add(valueColumn);
Random rng = new Random();
for (int i = 1 ; i <= 20 ; i++) {
table.getItems().add(new Item("Item "+i, rng.nextLong()));
}
primaryStage.setScene(new Scene(table, 600, 600));
primaryStage.show();
}
public static class Item {
private final StringProperty name = new SimpleStringProperty();
private final LongProperty value = new SimpleLongProperty();
public Item(String name, long 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 LongProperty valueProperty() {
return this.value;
}
public final long getValue() {
return this.valueProperty().get();
}
public final void setValue(final long value) {
this.valueProperty().set(value);
}
}
public static void main(String[] args) {
launch(args);
}
}
There is no need to set the Cellfactory, just set the CellValueFactory.
TableColumn<DataByCurrencyPairRow, String> columnTotal;
columnTotal.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<DataByCurrencyPairRow,String>, ObservableValue<String>>() {
#Override
public ObservableValue<String> call(CellDataFeatures<DataByCurrencyPairRow, String> param) {
DataByCurrencyPairRow value = param.getValue();
return new ReadOnlyStringWrapper(NumberFormat.getNumberInstance(Locale.US).format(123123)); //replace the number with the calculated total
}
});
I have TreeTableView with 2 columns, so I want to be able something like that:
user double click in cell -> Someclass.getType() returns type of editing field ->in cell I see this type of editing field(TextField or ChoiceBox)
wnen I need to use TextField only, i can use someshing like that
TreeColumn1.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
TreeColumn1.setOnEditCommit(firstColumnCommitHandler);
commitHandler:
private EventHandler<TreeTableColumn.CellEditEvent<SomeClass, String>> firstColumnCommitHandler = event -> {
final SomeClass item = event.getRowValue().getValue();
item.setVariable(event.getNewValue());
};
but i need different types, and have no idea howto do this
For this you need to implement the table cell yourself, and display the appropriate components when you go in and out of editing state. Here's a basic idea. The ChoiceBoxs look odd, you may need to work with some CSS to get them looking correct. In this example, if the box in the first column is checked, the second column will use a ChoiceBox for editing; otherwise it will use a TextField.
import java.util.function.Function;
import java.util.stream.IntStream;
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.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class TableWithVaryingEditor extends Application {
#Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
IntStream.rangeClosed(1, 20).mapToObj(i -> new Item("Item "+i)).forEach(table.getItems()::add);
TableColumn<Item, Boolean> fixedCol = column("Fixed", Item::fixedProperty);
table.getColumns().add(fixedCol);
fixedCol.setCellFactory(CheckBoxTableCell.forTableColumn(fixedCol));
TableColumn<Item, String> nameCol = column("Name", Item::nameProperty);
table.getColumns().add(nameCol);
nameCol.setCellFactory(col -> new TableCell<Item, String>() {
private TextField textField = new TextField();
private ChoiceBox<String> choice = new ChoiceBox<>();
private boolean ignoreChoiceBoxChange = false ;
// anonymous constructor:
{
choice.valueProperty().addListener((obs, oldValue, newValue) -> {
if (! ignoreChoiceBoxChange) {
commitEdit(newValue);
}
});
choice.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (! isNowFocused) {
cancelEdit();
}
});
choice.showingProperty().addListener((obs, wasShowing, isNowShowing) -> {
if (! isNowShowing) {
cancelEdit();
}
});
textField.setOnAction(e -> commitEdit(textField.getText()));
textField.focusedProperty().addListener((obs, wasFocused, isNowFocused) -> {
if (! isNowFocused) {
cancelEdit();
}
});
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (isEditing()) {
updateEditor();
} else {
updateText();
}
}
#Override
public void startEdit() {
super.startEdit();
updateEditor();
}
#Override
public void cancelEdit() {
super.cancelEdit();
updateText();
}
#Override
public void commitEdit(String item) {
super.commitEdit(item);
updateText();
}
private void updateEditor() {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
int index = getIndex();
Item item = getTableView().getItems().get(index);
if (item.isFixed()) {
ignoreChoiceBoxChange = true ;
choice.getItems().setAll(getItem(), "Choice 1", "Choice 2");
choice.getSelectionModel().select(getItem());
setGraphic(choice);
choice.show();
ignoreChoiceBoxChange = false ;
} else {
textField.setText(getItem());
setGraphic(textField);
}
}
private void updateText() {
setContentDisplay(ContentDisplay.TEXT_ONLY);
if (isEmpty()) {
setText(null);
} else {
setText(getItem());
}
}
});
primaryStage.setScene(new Scene(new BorderPane(table), 600, 400));
primaryStage.show();
}
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 BooleanProperty fixed = new SimpleBooleanProperty();
private final StringProperty name = new SimpleStringProperty();
public Item(String name) {
setName(name);
}
public final BooleanProperty fixedProperty() {
return this.fixed;
}
public final boolean isFixed() {
return this.fixedProperty().get();
}
public final void setFixed(final boolean fixed) {
this.fixedProperty().set(fixed);
}
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);
}
}