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);
}
};
}
}
Related
I have a ListView with the languages one employee can speak. It uses custom ListCells implemented with the help of setCellFactory. I have a ListCellFactory class where I stored the call for my custom ListCell. In my LanguageListCell (my custom ListCell class) I have a ContextMenu in which I have a MenuItem. The MenuItem fires an event to edit the selected ListCell. The only problem I have encountered in my project is having this double click editing. Whenever I click more than once (when the Cell is not selected) or once (when the Cell is selected) the startEdit gets called. What I want to accomplish is remove this double click editing. But what I have managed to write as a code causes too many problems. For example, when I click on the TextField which is used for the editing, the cancelEdit method is called. And, basically, I can't even click on the TextField without removing it.
See my code for reference
This is the LanguageListCell class
package application;
import javafx.beans.binding.Bindings;
import javafx.event.EventHandler;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
public class LanguageListCell extends ListCell<String> {
private TextField textField;
LanguageListCell cell = this;
int i = 0;
public LanguageListCell(ListView<String> languages) {
ContextMenu contextMenu = new ContextMenu();
cell.setEditable(true);
MenuItem editItem = new MenuItem();
editItem.textProperty().bind(Bindings.format("Edit \"%s\"", cell.itemProperty()));
editItem.setOnAction(event -> {
languages.edit(cell.getIndex());
//cell.startEdit();
});
contextMenu.getItems().add(editItem);
cell.textProperty().bind(cell.itemProperty());
cell.emptyProperty().addListener((obs, wasEmpty, isNowEmpty) -> {
if (isNowEmpty) {
cell.setContextMenu(null);
} else {
if (getString() != "Add") {
cell.setContextMenu(contextMenu);
}
}
});
//This is what I have tried but i get the issue with cancelEdit
//where when I press the TextField it cancels the editing
cell.addEventFilter(MouseEvent.MOUSE_PRESSED, (MouseEvent e) -> {
if (e.getButton().equals(MouseButton.PRIMARY)) {
if (cell.isSelected() && e.getClickCount() >= 1) {
languages.getSelectionModel().clearSelection();
languages.getSelectionModel().select(cell.getItem());
e.consume();
}
if (e.getClickCount() > 1) {
e.consume();
}
}
});
}
public String getString() {
return getItem() == null ? "" : getItem().toString();
}
#Override
public void startEdit() {
super.startEdit();
if (textField == null) {
createTextField();
}
setText(null);
setGraphic(textField);
textField.selectAll();
textField.requestFocus();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setGraphic(null);
setText(getItem());
textField = null;
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
cell.textProperty().unbind();
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(getGraphic());
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
if (textField.getText().trim() != "" && textField.getText().trim().length() > 3) {
commitEdit(textField.getText());
setGraphic(null);
setGraphic(getGraphic());
}
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
}
This is the LanguageCellFactory class
package application;
import javafx.beans.binding.Bindings;
import javafx.scene.Node;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.Pane;
import javafx.util.Callback;
import application.LanguageListCell;
public class LanguageCellFactory implements Callback<ListView<String>, ListCell<String>> {
public LanguageCellFactory()
{
}
#Override
public ListCell<String> call(ListView<String> languages)
{
return new LanguageListCell(languages);
}
}
And this is the Main class
package application;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.control.ListView;
import java.util.ArrayList;
import application.LanguageCellFactory;
public class Main extends Application {
private ListView<String> languages;
private Stage stage;
#Override
public void init()
{
}
public void start(Stage primaryStage) {
try {
Pane root = new Pane();
Scene scene = new Scene(root,800,600);
ArrayList<String> list_items = new ArrayList<String>();
list_items.add("Russian");
list_items.add("English");
languages = new ListView<String>();
languages.relocate(150, 62);
languages.getItems().addAll(list_items);
root.getChildren().add(languages);
//languages.setEditable(true);
languages.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
languages.setCellFactory(new LanguageCellFactory());
primaryStage.setScene(scene);
primaryStage.setTitle("First JavaFX App");
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
If someone wants to know how to fix this, I tried removing the editing events for the ListCell and provided my own "editing" behaviour for the cells and now it is working
Here is the modified version of the LanguageListCell class
package application;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
public class LanguageListCell extends ListCell<String> {
private TextField textField;
LanguageListCell cell = this;
int i = 0;
private boolean textfield_state = false;
ContextMenu contextMenu = new ContextMenu();
MenuItem editItem = new MenuItem();
public LanguageListCell(ListView<String> languages) {
editItem.textProperty().bind(Bindings.format("Edit \"%s\"", cell.itemProperty()));
contextMenu.getItems().add(editItem);
cell.textProperty().bind(cell.itemProperty());
cell.emptyProperty().addListener((obs, wasEmpty, isNowEmpty) -> {
if (isNowEmpty) {
cell.setContextMenu(null);
} else {
if (getString() != "Add") {
cell.setContextMenu(contextMenu);
}
}
});
}
public String getString() {
return getItem() == null ? "" : getItem().toString();
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
cell.textProperty().unbind();
//String thecellValue = getString();
if (empty) {
setText(null);
setGraphic(null);
} else {
if (textfield_state) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(getGraphic());
editItem.setOnAction(event -> {
textfield_state = true;
if(textField == null)
{
createTextField();
}
setText(null);
setGraphic(textField);
textField.requestFocus();
textField.selectAll();
});
}
}
}
private void createTextField() {
textField = new TextField(getString());
String CellValue = getString();
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
if (textField.getText().trim() != "" && textField.getText().trim().length() > 3) {
setText(textField.getText().trim());
setGraphic(null);
}
} else if (t.getCode() == KeyCode.ESCAPE) {
//cancelEdit();
setGraphic(null);
setText(CellValue);
textField = null;
}
}
});
textField.focusedProperty().addListener((obs, isNotFocused, isFocused) -> {
if(isFocused)
{
LanguageListCell parent = (LanguageListCell) textField.getParent();
ListView<String> list = parent.getListView();
list.getSelectionModel().clearSelection();
list.getSelectionModel().select(parent.getIndex());
}
else
{
setGraphic(null);
setText(CellValue);
textField = null;
}
});
}
}
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();
}
}
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);
}
}
I'm using the following code from Oracle:
import java.util.Arrays;
import java.util.List;
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.VBox;
public class TreeViewSample extends Application {
List<Employee> employees = Arrays.<Employee>asList(
new Employee("Ethan Williams", "Sales Department"),
new Employee("Emma Jones", "Sales Department"),
new Employee("Michael Brown", "Sales Department"),
new Employee("Anna Black", "Sales Department"),
new Employee("Rodger York", "Sales Department"),
new Employee("Susan Collins", "Sales Department"),
new Employee("Mike Graham", "IT Support"),
new Employee("Judy Mayer", "IT Support"),
new Employee("Gregory Smith", "IT Support"),
new Employee("Jacob Smith", "Accounts Department"),
new Employee("Isabella Johnson", "Accounts Department"));
TreeItem<String> rootNode =
new TreeItem<String>("MyCompany Human Resources");
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage stage) {
rootNode.setExpanded(true);
for (Employee employee : employees) {
TreeItem<String> empLeaf = new TreeItem<String>(employee.getName());
boolean found = false;
for (TreeItem<String> depNode : rootNode.getChildren()) {
if (depNode.getValue().contentEquals(employee.getDepartment())){
depNode.getChildren().add(empLeaf);
found = true;
break;
}
}
if (!found) {
TreeItem depNode = new TreeItem(employee.getDepartment());
rootNode.getChildren().add(depNode);
depNode.getChildren().add(empLeaf);
}
}
stage.setTitle("Tree View Sample");
VBox box = new VBox();
final Scene scene = new Scene(box, 400, 300);
scene.setFill(Color.LIGHTGRAY);
TreeView<String> treeView = new TreeView<String>(rootNode);
treeView.setEditable(true);
treeView.setCellFactory(new Callback<TreeView<String>,TreeCell<String>>(){
#Override
public TreeCell<String> call(TreeView<String> p) {
return new TextFieldTreeCellImpl();
}
});
box.getChildren().add(treeView);
stage.setScene(scene);
stage.show();
}
private final class TextFieldTreeCellImpl extends TreeCell<String> {
private TextField textField;
private ContextMenu addMenu = new ContextMenu();
public TextFieldTreeCellImpl() {
MenuItem addMenuItem = new MenuItem("Add Employee");
addMenu.getItems().add(addMenuItem);
addMenuItem.setOnAction(new EventHandler() {
public void handle(Event t) {
TreeItem newEmployee =
new TreeItem<String>("New Employee");
getTreeItem().getChildren().add(newEmployee);
}
});
}
#Override
public void startEdit() {
super.startEdit();
if (textField == null) {
createTextField();
}
setText(null);
setGraphic(textField);
textField.selectAll();
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText((String) getItem());
setGraphic(getTreeItem().getGraphic());
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
if (isEditing()) {
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(getTreeItem().getGraphic());
if (
!getTreeItem().isLeaf()&&getTreeItem().getParent()!= null
){
setContextMenu(addMenu);
}
}
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
if (t.getCode() == KeyCode.ENTER) {
commitEdit(textField.getText());
} else if (t.getCode() == KeyCode.ESCAPE) {
cancelEdit();
}
}
});
}
private String getString() {
return getItem() == null ? "" : getItem().toString();
}
}
public static class Employee {
private final SimpleStringProperty name;
private final SimpleStringProperty department;
private Employee(String name, String department) {
this.name = new SimpleStringProperty(name);
this.department = new SimpleStringProperty(department);
}
public String getName() {
return name.get();
}
public void setName(String fName) {
name.set(fName);
}
public String getDepartment() {
return department.get();
}
public void setDepartment(String fName) {
department.set(fName);
}
}
}
This code produces a GUI with a very basic, editable TreeView. However, when clicking around the cells that populate the tree, eventually, the text fields used for editing will begin to display incorrect information (information that is contained within the tree, but that is not represented by the cell being edited). I do not understand why this is happening, and I haven't found any reference of this happening anywhere on Google or elsewhere on StackOverflow. If anyone could help me understand why this is occuring, I'd be very happy.
Thanks!
I checked your application and I have found only one weird thing. When you create the edit textfield once, the unsaved information will be visible in that textfield later.
This modification will solve it:
# Override
public void cancelEdit() {
super.cancelEdit();
this.setText(this.getItem());
this.textField.setText(this.getItem());
this.setGraphic(this.getTreeItem().getGraphic());
}
I want to trigger a method or action when the user check or uncheck a checkbox in the tableView. the coursData.addListener(...) doesn't get triggered when the user use the checkBox .
Here is my code it compiles and the windows appears with the tableView with checkbox.
package testCheckBox2;
import javafx.application.Application;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBase;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class CheckBoxTableCellTest extends Application {
private TableView<cours> tableView;
public ObservableList<cours> coursData ;//= FXCollections.observableArrayList();
#Override
public void start(Stage primaryStage) {
this.tableView = new TableView<cours>();
final TableColumn<cours, String> Cours = new TableColumn<cours, String>("Cours");
final TableColumn<cours, Boolean> checkedCol = new TableColumn<cours, Boolean>("Checked");
this.coursData =FXCollections.observableArrayList(
new cours("Analyse", "3"),
new cours("Analyse TP", "4"),
new cours("Thermo", "5"),
new cours("Thermo TP", "7"),
new cours("Chimie", "8"));
tableView.setItems(this.coursData);
tableView.getColumns().addAll(Cours, checkedCol);
Cours.setCellValueFactory(new PropertyValueFactory<cours, String>("cours"));
checkedCol.setCellValueFactory(new PropertyValueFactory<cours, Boolean>("checked"));
checkedCol.setCellFactory(CheckBoxTableCell.forTableColumn(checkedCol));
checkedCol.setEditable(true);
tableView.setEditable(true);
final BorderPane root = new BorderPane();
root.setCenter(tableView);
coursData.addListener(new InvalidationListener() {
#Override public void invalidated(Observable o) {
System.out.println("checkBox change state ");
//Here is my problem.
//When the user click on a checkBox , the method isn't call .
}
});
Scene scene = new Scene(root, 300, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
public static class cours {
private StringProperty cours;
private StringProperty coursID;
private BooleanProperty checked;
public cours(String cours, String coursID) {
this.cours = new SimpleStringProperty(cours);
this.coursID = new SimpleStringProperty(coursID);
this.checked = new SimpleBooleanProperty(false);
}
public String getCours() {
return cours.get();
}
public String getCoursID() {
return coursID.get();
}
public boolean isChecked() {
return checked.get();
}
public void setCours(String cours) {
this.cours.set(cours);
}
public void setCoursID(String coursID) {
this.coursID.set(coursID);
}
public void setChecked(boolean checked) {
this.checked.set(checked);
}
public StringProperty coursProperty() {
return cours;
}
public StringProperty coursIDProperty() {
return coursID;
}
public BooleanProperty checkedProperty() {
return checked;
}
}
}
You have two ways of getting a notification when any of the check boxes is clicked.
One: providing a callback as argument for CheckBoxTableCell.forTableColumn instead of the tableColumn:
checkedCol.setCellFactory(CheckBoxTableCell.forTableColumn(new Callback<Integer, ObservableValue<Boolean>>() {
#Override
public ObservableValue<Boolean> call(Integer param) {
System.out.println("Cours "+items.get(param).getCours()+" changed value to " +items.get(param).isChecked());
return items.get(param).checkedProperty();
}
}));
Two: providing a callback to the collection:
final List<Cours> items=Arrays.asList(new Cours("Analyse", "3"),
new Cours("Analyse TP", "4"),
new Cours("Thermo", "5"),
new Cours("Thermo TP", "7"),
new Cours("Chimie", "8"));
this.coursData = FXCollections.observableArrayList(new Callback<Cours, Observable[]>() {
#Override
public Observable[] call(Cours param) {
return new Observable[] {param.checkedProperty()};
}
});
coursData.addAll(items);
and now listening to changes in the collection:
coursData.addListener(new ListChangeListener<Cours>() {
#Override
public void onChanged(ListChangeListener.Change<? extends Cours> c) {
while (c.next()) {
if (c.wasUpdated()) {
System.out.println("Cours "+items.get(c.getFrom()).getCours()+" changed value to " +items.get(c.getFrom()).isChecked());
}
}
}
});