I have an app that makes significant use of checkboxes in JavaFX TreeView and TableView. There is custom code as much of it was done before the enhancements that came later in JavaFX 2.2.
I now find that checkboxes that used to work do not work (as if they are disabled) although some work intermittently.
I have checked through the Oracle compatibility documentation and I can find nothing relevant.
I have a small sample app that works perfectly in Java 7.4 but shows the same faulty behaviour as the main app in Java 8.
Could anyone you please suggest where I might start looking from your own experience - e.g. cell factory, callback etc - or indicate if anything fundamental changed with this kind of construct at Java 8? I have posted the sample app code below (four separate classes).
Thank you in advance.
package samplefx2_original_v7;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Callback;
public class SampleFX2_Original_V7 extends Application {
private TableView table = new TableView();
private boolean everything = false;
//Sample data for the table.
private final ObservableList<Person> data =
FXCollections.observableArrayList(
new ControlPerson(false, "Select Columns", false, false),
new Person(true, "Jacob Smith", true, false),
new Person(true, "Isabella Johnson", true, true),
new Person(true, "Ethan Williams", false, false),
new Person(true, "Emma Jones", false, true),
new Person(false, "Michael Brown", true, true));
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage) {
Scene scene = new Scene(new Group());
stage.setTitle("Table View Sample");
stage.setWidth(850);
stage.setHeight(500);
final Label label = new Label("Address Book");
label.setFont(new Font("Arial", 20));
//A custom cell factory that creates checkboxes for a boolean property.
Callback<TableColumn, TableCell> colCheckFactory = new Callback<TableColumn, TableCell>() {
#Override
public TableCell call(TableColumn p) {
return new CheckBoxCell();
}
};
//The various columns
TableColumn nameCol = new TableColumn("Name");
nameCol.setMinWidth(100);
nameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("name"));
TableColumn contactCol = createContactColumn(colCheckFactory);
TableColumn emailCol = createEmailColumn(colCheckFactory);
TableColumn phoneCol = createPhoneColumn(colCheckFactory);
//Add the columns and data to the table.
table.setItems(data);
table.getColumns().addAll(nameCol, contactCol, emailCol, phoneCol);
//Make the table editable
table.setEditable(true);
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
final VBox vbox = new VBox();
vbox.setSpacing(5);
vbox.getChildren().addAll(label, table);
vbox.setPadding(new Insets(10, 0, 0, 10));
((Group) scene.getRoot()).getChildren().addAll(vbox);
stage.setScene(scene);
stage.show();
}
private TableColumn createEmailColumn(Callback<TableColumn, TableCell> cellFactory) {
TableColumn emailCol = new TableColumn("Email");
emailCol.setMinWidth(75);
emailCol.setCellValueFactory(new PropertyValueFactory("email"));
emailCol.setCellFactory(cellFactory);
emailCol.setOnEditCommit(new EventHandler<CellEditEvent<Person, Boolean>>() {
#Override
public void handle(CellEditEvent<Person, Boolean> event) {
if (event.getRowValue() instanceof ControlPerson) {
for (Person p : data) {
p.setEmail(event.getNewValue());
}
} else {
//Need to handle the indivdual cells as well as the special control cells.
Person p = event.getRowValue();
p.setEmail( event.getNewValue() );
}
}
});
return emailCol;
}
private TableColumn createPhoneColumn(Callback<TableColumn, TableCell> cellFactory) {
TableColumn phoneCol = new TableColumn("Phone");
phoneCol.setMinWidth(75);
phoneCol.setCellValueFactory( new PropertyValueFactory("phone"));
phoneCol.setCellFactory(cellFactory);
phoneCol.setOnEditCommit(new EventHandler<CellEditEvent<Person, Boolean>>() {
#Override
public void handle(CellEditEvent<Person, Boolean> event) {
if (event.getRowValue() instanceof ControlPerson) {
for (Person p : data) {
p.setPhone(event.getNewValue());
}
} else {
Person p = event.getRowValue();
p.setPhone( event.getNewValue() );
}
}
});
return phoneCol;
}
/**
* This is the main control column in your application (containing the green and red circles).
*
* #param cellFactory
* #return
*/
private TableColumn createContactColumn( Callback<TableColumn, TableCell> cellFactory ) {
TableColumn contactCol = new TableColumn("Contact");
contactCol.setMinWidth(75);
contactCol.setCellValueFactory( new PropertyValueFactory( "contact"));
contactCol.setCellFactory(cellFactory);
contactCol.setOnEditCommit(new EventHandler<CellEditEvent<Person, Boolean>>() {
#Override
public void handle(CellEditEvent<Person, Boolean> event) {
//This handler is different to the other two as it controls the checking/unchecking of both
//the whole table and individual rows.
if (event.getRowValue() instanceof ControlPerson) {
for (Person p : data) {
p.setContact(event.getNewValue());
p.setEmail(event.getNewValue());
p.setPhone(event.getNewValue());
}
//This is just an example of how you would control a special "everything" flag.
//You could call any method or take any action here to deal with the special
//case where everything is selected.
everything = event.getNewValue();
} else {
//Set the state of any boolean properties to modify the whole row.
Person p = event.getRowValue();
p.setContact( event.getNewValue() );
p.setEmail(event.getNewValue());
p.setPhone(event.getNewValue());
}
}
});
return contactCol;
}
}
package samplefx2_original_v7;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
public class Person {
private final SimpleBooleanProperty contact;
private final SimpleStringProperty name;
private final SimpleBooleanProperty email;
private final SimpleBooleanProperty phone;
public Person(boolean contact, String name, boolean email, boolean phone) {
this.contact = new SimpleBooleanProperty( contact );
this.name = new SimpleStringProperty(name);
this.email = new SimpleBooleanProperty( email );
this.phone = new SimpleBooleanProperty( phone );
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
public SimpleStringProperty nameProperty() {
return name;
}
public boolean isContact() {
return contact.get();
}
public void setContact( boolean contact ) {
this.contact.set( contact );
}
public SimpleBooleanProperty contactProperty() {
return contact;
}
public boolean isEmail() {
return email.get();
}
public void setEmail( boolean email ) {
this.email.set( email );
}
public SimpleBooleanProperty emailProperty() {
return email;
}
public boolean isPhone() {
return contact.get();
}
public void setPhone( boolean phone ) {
this.phone.set( phone );
}
public SimpleBooleanProperty phoneProperty() {
return phone;
}
}
package samplefx2_original_v7;
/**
* This subclass of Person is used only to identify the row(s) that is used to control
* the checking and unchecking of columns.
*/
public class ControlPerson extends Person {
public ControlPerson(boolean active, String name, boolean email, boolean phone) {
super(active, name, email, phone);
}
}
package samplefx2_original_v7;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
public class CheckBoxCell extends TableCell<Person, Boolean> {
private CheckBox checkBox;
public CheckBoxCell() {
if (checkBox == null) {
checkBox = new CheckBox();
checkBox.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
CheckBox cb = (CheckBox) event.getSource();
getTableView().edit(getTableRow().getIndex(), getTableColumn());
commitEdit(cb.isSelected());
}
});
}
setGraphic(checkBox);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
//Center align the checkboxes.
setAlignment(Pos.CENTER);
}
#Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (item == null) {
//If we don't have an item don't draw the checkbox.
setGraphic( null );
// checkBox.setDisable(true);
// checkBox.setSelected(false);
} else {
checkBox.setDisable(false);
checkBox.setSelected(item);
}
}
}
A couple of things are going on here. A simple thing to note is that you don't set the graphic when the cell changes from empty to non-empty. You need:
#Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (item == null) {
//If we don't have an item don't draw the checkbox.
setGraphic( null );
// checkBox.setDisable(true);
// checkBox.setSelected(false);
} else {
checkBox.setDisable(false);
checkBox.setSelected(item);
setGraphic(checkBox);
}
}
But probably more of an issue is that your table cell implementation really relies on undocumented behavior, in that it forces the table into editing mode (getTableView().edit(...)) and (as far as I can tell) seems to rely on some side effects of this in order to get the functionality you want. Because the behavior is undocumented, there's no guarantee it will remain the same in new releases, so I'm not too surprised it breaks under Java 8.
A better way to implement the cells is just to use listeners (or bindings) to exchange data between the check box's selectedProperty and the property in the Person class. Because the display is always the same (i.e. a check box) no matter whether the cell is being edited or not, there's no really need to work with the editing API from the cell class. This is the approach the standard CheckBoxTableCell introduced in JavaFX 2.2 works.
Assuming the functionality you want is that the "contact" column check boxes should select both the "email" and "phone" checkboxes, I would approach this slightly differently. There's really no logical need to have a contact property in the model class (Person), because that information is already incorporated in the other two boolean properties. (Another way to think of this is that the "control" checkboxes are just UI components and not really part of the data, so they have no place in the model.) Your "contact" column doesn't really have a property associated with it; just make that a TableColumn<Person, Person> and have its cells update the other properties appropriately.
Also, your ControlPerson is a bit of a hack. Again this row in the table is really just for user interface purposes and is not part of the data. So it really shouldn't appear as part of the table's items. I would put the "select all" check boxes in the table header, not in the data part of the table. This will change their appearance some (though you can always fix that with CSS if you need) but the benefit is that they remain visible when you scroll, which is probably desirable.
Here's a complete example which works this way, and runs under both Java 7 (JavaFX 2.2) and under Java 8. (I tested under 1.7.0_65, 1.8.0_25, and 1.8.0_40.)
import java.util.Arrays;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class SampleFX2 extends Application {
#Override
public void start(Stage primaryStage) {
ObservableList<Person> data = FXCollections.observableArrayList(
new Callback<Person, Observable[]>() {
#Override
public Observable[] call(Person person) {
return new Observable[] {person.emailProperty(), person.phoneProperty()};
}
});
data.addAll(Arrays.asList(
new Person("Jacob Smith", true, false),
new Person("Isabella Johnson", true, true),
new Person("Ethan Williams", false, false),
new Person("Emma Jones", false, true),
new Person("Michael Brown", true, true)));
final TableView<Person> table = new TableView<>();
table.setItems(data);
TableColumn<Person, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<Person, String>("name"));
final CheckBox allContactCheckBox = new CheckBox() ;
final CheckBox allEmailCheckBox = new CheckBox();
final CheckBox allPhoneCheckBox = new CheckBox();
updateHeaderCheckBoxes(table, allContactCheckBox, allEmailCheckBox, allPhoneCheckBox);
data.addListener(new ListChangeListener<Person>() {
#Override
public void onChanged(Change<? extends Person> change) {
updateHeaderCheckBoxes(table, allContactCheckBox, allEmailCheckBox, allPhoneCheckBox);
}
});
allPhoneCheckBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> obs,
Boolean wasSelected, Boolean isSelected) {
for (Person person : table.getItems()) {
person.setPhone(isSelected);
}
}
});
allEmailCheckBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> obs,
Boolean wasSelected, Boolean isSelected) {
for (Person person : table.getItems()) {
person.setEmail(isSelected);
}
}
});
allContactCheckBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> obs,
Boolean wasSelected, Boolean isSelected) {
for (Person person : table.getItems()) {
person.setPhone(isSelected);
person.setEmail(isSelected);
}
}
});
TableColumn<Person, Person> contactCol = new TableColumn<>();
contactCol.setPrefWidth(75);
contactCol.setGraphic(createTableHeader("Contact", allContactCheckBox));
contactCol.setCellValueFactory(new Callback<CellDataFeatures<Person, Person>, ObservableValue<Person>>() {
#Override
public ObservableValue<Person> call(
CellDataFeatures<Person, Person> person) {
return new ReadOnlyObjectWrapper<>(person.getValue());
}
});
contactCol.setCellFactory(new Callback<TableColumn<Person, Person>, TableCell<Person, Person>>() {
#Override
public TableCell<Person, Person> call(
TableColumn<Person, Person> param) {
return new ContactCell();
}
});
TableColumn<Person, Boolean> emailCol = new TableColumn<>();
emailCol.setPrefWidth(75);
emailCol.setGraphic(createTableHeader("Email", allEmailCheckBox));
emailCol.setCellValueFactory(new PropertyValueFactory<Person, Boolean>("email"));
emailCol.setCellFactory(new Callback<TableColumn<Person, Boolean>, TableCell<Person, Boolean>>() {
#Override
public TableCell<Person, Boolean> call(
TableColumn<Person, Boolean> column) {
return new CheckBoxCell<Person>(new Callback<Person, BooleanProperty>() {
#Override
public BooleanProperty call(Person person) {
return person.emailProperty();
}
});
}
});
TableColumn<Person, Boolean> phoneCol = new TableColumn<>();
phoneCol.setPrefWidth(75);
phoneCol.setGraphic(createTableHeader("Phone", allPhoneCheckBox));
phoneCol.setCellValueFactory(new PropertyValueFactory<Person, Boolean>("phone"));
phoneCol.setCellFactory(new Callback<TableColumn<Person, Boolean>, TableCell<Person, Boolean>>() {
#Override
public TableCell<Person, Boolean> call(
TableColumn<Person, Boolean> column) {
return new CheckBoxCell<Person>(new Callback<Person, BooleanProperty>() {
#Override
public BooleanProperty call(Person person) {
return person.phoneProperty();
}
});
}
});
table.getColumns().add(nameCol);
table.getColumns().add(contactCol);
table.getColumns().add(emailCol);
table.getColumns().add(phoneCol);
Button showButton = new Button("Debug");
showButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent evt) {
for (Person p : table.getItems()) {
System.out.println(p.getName() + " " + p.isEmail() +" "+ p.isPhone());
}
System.out.println();
}
});
HBox controls = new HBox(5);
controls.setAlignment(Pos.CENTER);
controls.setPadding(new Insets(10));
controls.getChildren().add(showButton);
BorderPane root = new BorderPane();
root.setCenter(table);
root.setBottom(controls);
Scene scene = new Scene(root, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private Node createTableHeader(String text, CheckBox checkBox) {
VBox header = new VBox(2);
header.setPadding(new Insets(2));
header.getChildren().addAll(new Label(text), checkBox);
header.setAlignment(Pos.CENTER);
return header ;
}
private static void updateHeaderCheckBoxes(final TableView<Person> table,
final CheckBox allContactCheckBox, final CheckBox allEmailCheckBox,
final CheckBox allPhoneCheckBox) {
boolean allPhoneSelected = true ;
boolean noPhoneSelected = true ;
boolean allEmailSelected = true ;
boolean noEmailSelected = true ;
for (Person person : table.getItems()) {
if (person.isEmail()) {
noEmailSelected = false ;
} else {
allEmailSelected = false ;
}
if (person.isPhone()) {
noPhoneSelected = false ;
} else {
allPhoneSelected = false ;
}
}
setCheckBoxState(allPhoneSelected, noPhoneSelected, allPhoneCheckBox);
setCheckBoxState(allEmailSelected, noEmailSelected, allEmailCheckBox);
setCheckBoxState(allPhoneSelected && allEmailSelected, noPhoneSelected && noEmailSelected, allContactCheckBox);
}
private static void setCheckBoxState(boolean on, boolean off, CheckBox checkBox) {
if (on) {
checkBox.setIndeterminate(false);
checkBox.setSelected(true);
} else if (off) {
checkBox.setIndeterminate(false);
checkBox.setSelected(false);
} else {
checkBox.setIndeterminate(true);
}
}
public static class ContactCell extends TableCell<Person, Person> {
private final CheckBox checkBox ;
public ContactCell() {
checkBox = new CheckBox();
final ChangeListener<Boolean> propertyListener = new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> obs,
Boolean oldValue, Boolean newValue) {
updateCheckBox();
}
};
checkBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> obs,
Boolean oldValue, Boolean newValue) {
Person person = getItem();
person.setEmail(newValue);
person.setPhone(newValue);
}
});
itemProperty().addListener(new ChangeListener<Person>() {
#Override
public void changed(
ObservableValue<? extends Person> observable,
Person oldValue, Person newValue) {
if (oldValue != null) {
oldValue.emailProperty().removeListener(propertyListener);
oldValue.phoneProperty().removeListener(propertyListener);
}
if (newValue != null) {
newValue.emailProperty().addListener(propertyListener);
newValue.phoneProperty().addListener(propertyListener);
updateCheckBox();
}
}
});
emptyProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> obs,
Boolean wasEmpty, Boolean isEmpty) {
if (isEmpty) {
setGraphic(null);
} else {
setGraphic(checkBox);
}
}
});
setAlignment(Pos.CENTER);
}
private void updateCheckBox() {
Person person = getItem();
if (person != null) {
setCheckBoxState(person.isEmail() && person.isPhone(), ! person.isEmail() && ! person.isPhone(), checkBox);
if (person.isEmail() && person.isPhone()) {
checkBox.setSelected(true);
} else if (! person.isEmail() && ! person.isPhone()) {
checkBox.setSelected(false);
} else {
checkBox.setIndeterminate(true);
}
checkBox.setIndeterminate(person.isEmail() != person.isPhone());
}
}
}
public static class CheckBoxCell<T> extends TableCell<T, Boolean> {
private final CheckBox checkBox ;
public CheckBoxCell(final Callback<T, BooleanProperty> propertyMapper) {
checkBox = new CheckBox();
checkBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(
ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue) {
#SuppressWarnings("unchecked") // for some reason getTableRow() returns a raw type...
TableRow<T> row = getTableRow();
if (row != null) {
T item = row.getItem();
if (item != null) {
BooleanProperty property = propertyMapper.call(item);
property.set(newValue);
}
}
}
});
itemProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(
ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue) {
if (newValue != null) {
checkBox.setSelected(newValue);
}
}
});
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setAlignment(Pos.CENTER);
emptyProperty().addListener(new ChangeListener<Boolean>() {
#Override
public void changed(
ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue) {
if (newValue) {
setGraphic(null);
} else {
setGraphic(checkBox);
}
}
});
}
}
public static class Person {
private final StringProperty name;
private final BooleanProperty email;
private final BooleanProperty phone;
public Person(String name, boolean email, boolean phone) {
this.name = new SimpleStringProperty(name);
this.email = new SimpleBooleanProperty( email );
this.phone = new SimpleBooleanProperty( phone );
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
public StringProperty nameProperty() {
return name;
}
public boolean isEmail() {
return email.get();
}
public void setEmail( boolean email ) {
this.email.set( email );
}
public BooleanProperty emailProperty() {
return email;
}
public boolean isPhone() {
return phone.get();
}
public void setPhone( boolean phone ) {
this.phone.set( phone );
}
public BooleanProperty phoneProperty() {
return phone;
}
}
public static void main(String[] args) {
launch(args);
}
}
I have made a TreeView, represented by a custom cellFactory where each cell is represented by an HBox looking like this.
How can I access the checkbox so that if you check it, a private boolean field in the corresponding EventTreeItem changes it's value?
Code:
public class EventTreeItem extends TreeItem<String>{
SimpleStringProperty item;
boolean important = true;
public EventTreeItem(boolean important, int id){
this.noNode = noNode;
super.setValue(id);
}
public EventTreeItem(){
}
public void setImportant(Boolean important){
this.important = important;
}
}
CellFactory:
public final class CustomTreeCellFactory extends TreeCell<String>{
private TextField textField;
private HBox hBox;
private HBox hBoxLeaf;
public CustomTreeCellFactory(){
try {
hBox = (HBox) FXMLLoader.load(getClass().getResource("/Views/TreCell.fxml"));
} catch (IOException e) {
System.out.println("This didn't work");
e.printStackTrace();
}
try {
hBoxLeaf = (HBox) FXMLLoader.load(getClass().getResource("/Views/TreCellLowestLevel.fxml"));
} catch (IOException e) {
System.out.println("This didn't work");
e.printStackTrace();
}
hBox.setAlignment(Pos.CENTER_LEFT);
hBoxLeaf.setAlignment(Pos.CENTER_LEFT);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
EventTreeItem eventTreeItem = (EventTreeItem) getTreeItem();
if (getTreeView().getTreeItemLevel(getTreeItem())==1) {
setGraphic(this.hBox);
((CheckBox) ((HBox)getGraphic()).getChildren().get(3)).setSelected(((EventTreeItem) getTreeItem()).important);
((Label) hBox.getChildren().get(0)).setText(eventTreeItem.noNode.getEntryNumber() + " " + eventTreeItem.noNode.getClass().getName().split("\\.")[3]);
((Label) hBox.getChildren().get(1)).setText(eventTreeItem.noNode.getDate().toString());
}else if (getTreeView().getTreeItemLevel(getTreeItem())==2){
setGraphic(this.hBoxLeaf);
}
} else {
setGraphic(null);
}
}
}
NodeTreeView
public class NodeTreeView implements ChartView{
private FilteredListModel filteredListModel;
TreeItem<String> root;
AnchorPane parent;
TreeView treeView;
public NodeTreeView(FilteredListModel filteredListModel, TabPane tabPane) throws IOException {
this.filteredListModel = filteredListModel;
parent = (AnchorPane) FXMLLoader.load(getClass().getResource("/Views/TryTreeViewInAnchorPane.fxml"));
parent.setVisible(true);
generateTree();
}
private void generateTree() {
this.root = new EventTreeItem();
root.setExpanded(true);
filteredListModel.makeEventNodeArrays().forEach(node->{
EventTreeItem item = new EventTreeItem((EventNoNode) node);
EventTreeItem item2 = new EventTreeItem();
root.getChildren().add(item);
item.getChildren().add(item2);
});
treeView = (TreeView) parent.getChildren().get(0);
treeView.setRoot(root);
treeView.setShowRoot(false);
treeView.setEditable(true);
treeView.setCellFactory(new Callback<TreeView<String>, TreeCell<String>>() {
#Override
public TreeCell<String> call(TreeView<String> param) {
return new CustomTreeCellFactory();
}
});
}
}
You can add a listener to checkbox's selectedProperty in your CustomTreeCellFactory constructor (which is not a factory, btw; you should call it CustomTreeCell instead):
public CustomTreeCellFactory() {
// ...
CheckBox checkbox = ...;
checkbox.selectedProperty().addListener((obs, wasSelected, isSelected) -> {
((EventTreeItem) getTreeItem()).important = isSelected;
});
}
Btw, it is probably a better idea to make the "important" flag be part of the item, i.e. instead of TreeView<String>, you would have TreeView<MyItem> where MyItem is
class MyItem {
String item;
boolean important;
MyItem(String item, boolean important) {
this.item = item;
this.important = important;
}
}
i need help about settings the combobox buttonCell.
I use a combobox that show data from an observable list that contains data from a table with two columns, "Step" and "NextStep" (NextStep contains one item inserted in column Step); what i need to do is to show the combobox listcell with the list of "Step" and the buttoncell with the relative "NextStep". Now, i can see the listcell correctly but my buttoncell is always empty.
The code:
// SET THE VALUE STEP TO THE LISTCELL
comboStatoSuccessivo.setCellFactory(new Callback<ListView<StatoEsiti>, ListCell<StatoEsiti>>() {
#Override public ListCell<StatoEsiti> call(ListView<StatoEsiti> p) {
return new ListCell<StatoEsiti>() {
#Override
protected void updateItem(StatoEsiti t, boolean bln) {
super.updateItem(t, bln);
if(t != null){
setText(t.statoProperty().getValue());
System.out.println("SET PROPERTY " + t.statoProperty().getValue());
} else {
setText(null);
}
}
};
}
});
// SET THE VALUE NEXTSTEP TO THE BUTTONCELL
comboStatoSuccessivo.setButtonCell(new ListCell<StatoEsiti>() {
#Override
protected void updateItem(StatoEsiti t, boolean bln) {
super.updateItem(t, bln);
if (t != null) { <<<<<<<<<<<<<<-------------ALWAYS NULL----WHY??????
setText(t.statoSuccessivoProperty().getValue());
System.out.println("SET PROPERTY BUTTONCELL " + t.statoSuccessivoProperty().getValue());
} else {
setText(null);
System.out.println("SET PROPERTY BUTTONCELL NULL");
}
}
});
Thanks in advance.
I have looked into your use case with the following demo SSCCE code.
It is working as expected, like as when the item is selected from the combobox's dropmenu the buttoncell is updated with related "nextStep":
public class ComboDemo extends Application {
#Override
public void start(Stage primaryStage) {
List<Person> list = new ArrayList<Person>();
list.add(new Person("step 1212", 12));
list.add(new Person("step 4545", 45));
list.add(new Person("step 5656", 56));
list.add(new Person("step 9090", 90));
ComboBox<Person> comboBox = new ComboBox<>(FXCollections.observableList(list));
comboBox.setCellFactory(new Callback<ListView<Person>, ListCell<Person>>() {
#Override
public ListCell<Person> call(ListView<Person> p) {
return new ListCell<Person>() {
#Override
protected void updateItem(Person t, boolean bln) {
super.updateItem(t, bln);
if (t != null) {
setText(t.getStepProperty().getValue());
System.out.println("SET PROPERTY " + t.getStepProperty().getValue());
} else {
setText(null);
}
}
};
}
});
// SET THE VALUE NEXTSTEP TO THE BUTTONCELL
comboBox.setButtonCell(new ListCell<Person>() {
#Override
protected void updateItem(Person t, boolean bln) {
super.updateItem(t, bln);
if (t != null) {
setText(t.getNextStepProperty().getValue().toString());
System.out.println("SET PROPERTY BUTTONCELL " + t.getNextStepProperty().getValue());
} else {
setText(null);
System.out.println("SET PROPERTY BUTTONCELL NULL");
}
}
});
StackPane root = new StackPane();
root.getChildren().add(comboBox);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
public static class Person {
private StringProperty stepProperty = new SimpleStringProperty();
private IntegerProperty nextStepProperty = new SimpleIntegerProperty();
public Person(String step, Integer nextStep) {
this.stepProperty.setValue(step);
this.nextStepProperty.setValue(nextStep);
}
public StringProperty getStepProperty() {
return stepProperty;
}
public void setStepProperty(StringProperty stepProperty) {
this.stepProperty = stepProperty;
}
public IntegerProperty getNextStepProperty() {
return nextStepProperty;
}
public void setNextStepProperty(IntegerProperty nextStepProperty) {
this.nextStepProperty = nextStepProperty;
}
}
public static void main(String[] args) {
launch(args);
}
}
Compare it with yours.
I have a combobox which shows list of User objects. I have coded a custom cell factory for the combobox:
#FXML ComboBox<User> cmbUserIds;
cmbUserIds.setCellFactory(new Callback<ListView<User>,ListCell<User>>(){
#Override
public ListCell<User> call(ListView<User> l){
return new ListCell<User>(){
#Override
protected void updateItem(Useritem, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
setText(item.getId()+" "+item.getName());
}
}
} ;
}
});
ListView is showing a string(id+name), but when I select an item from listview, Combobox is showing toString() method return value i.e address of object.
I can't override toString() method, because the User domain object should be same as the one at server.
How to display id in combobox? Please suggest
EDIT1
I tried below code. Now combo box shows id when I select a value from the listview.
cmbUserIds.setConverter(new StringConverter<User>() {
#Override
public String toString(User user) {
if (user== null){
return null;
} else {
return user.getId();
}
}
#Override
public User fromString(String id) {
return null;
}
});
The selected value in combo box is cleared when control focus is lost. How to fix this?
EDIT2:
#FXML AnchorPane root;
#FXML ComboBox<UserDTO> cmbUsers;
List<UserDTO> users;
public class GateInController implements Initializable {
#Override
public void initialize(URL location, ResourceBundle resources) {
users = UserService.getListOfUsers();
cmbUsers.setItems(FXCollections.observableList(users));
cmbUsers.getSelectionModel().selectFirst();
// list of values showed in combo box drop down
cmbUsers.setCellFactory(new Callback<ListView<UserDTO>,ListCell<UserDTO>>(){
#Override
public ListCell<UserDTO> call(ListView<UserDTO> l){
return new ListCell<UserDTO>(){
#Override
protected void updateItem(UserDTO item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
setText(item.getUserId()+" "+item.getUserNm());
}
}
} ;
}
});
//selected value showed in combo box
cmbUsers.setConverter(new StringConverter<UserDTO>() {
#Override
public String toString(UserDTO user) {
if (user == null){
return null;
} else {
return user.getUserId();
}
}
#Override
public UserDTO fromString(String userId) {
return null;
}
});
}
}
Just create and set a CallBack like follows:
#FXML ComboBox<User> cmbUserIds;
Callback<ListView<User>, ListCell<User>> cellFactory = new Callback<ListView<User>, ListCell<User>>() {
#Override
public ListCell<User> call(ListView<User> l) {
return new ListCell<User>() {
#Override
protected void updateItem(User item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
setText(item.getId() + " " + item.getName());
}
}
} ;
}
}
// Just set the button cell here:
cmbUserIds.setButtonCell(cellFactory.call(null));
cmbUserIds.setCellFactory(cellFactory);
You need to provide a functional fromString() Method within the Converter!
I had the same problem as you have and as I implemented the fromString() with working code, the ComboBox behaves as expected.
This class provides a few of my objects, for dev-test purposes:
public class DevCatProvider {
public static final CategoryObject c1;
public static final CategoryObject c2;
public static final CategoryObject c3;
static {
// Init objects
}
public static CategoryObject getCatForName(final String name) {
switch (name) {
case "Kategorie 1":
return c1;
case "Cat 2":
return c2;
case "Steuer":
return c3;
default:
return c1;
}
}
}
The converter object:
public class CategoryChooserConverter<T> extends StringConverter<CategoryObject> {
#Override
public CategoryObject fromString(final String catName) {
//This is the important code!
return Dev_CatProvider.getCatForName(catName);
}
#Override
public String toString(final CategoryObject categoryObject) {
if (categoryObject == null) {
return null;
}
return categoryObject.getName();
}
}
Can someone show me how to disable some item of my combobox (With FXML or Java code)? here is my ComboBox:
<ComboBox fx:id="cBox">
<items>
<FXCollections fx:factory="observableArrayList">
<String fx:value="Easy" />
<String fx:value="Normal" />
<String fx:value="Hard" />
</FXCollections>
</items>
</ComboBox>
Thanks!
i didn't found any methods that can inactive ComboBox items. You can try this work around , below code is to display sublist of items dynamically(use this idea to solve your problem).
private final ObservableList<String> allOptions =
FXCollections.observableArrayList("Easy","Normal","Hard");
// method which returns sublist we need
private ObservableList<String> getSubList(int start,int end) {
final ObservableList<String> toBeDisplayedList = FXCollections
.<String> observableArrayList();
toBeDisplayedList.addAll(allOptions.subList(start, end));
return toBeDisplayedList;
}
// now main logic
if(displayAll) {
comboBox.setItems(allOptions);
}
if(display only easy and normal) {
comboBox.setItems(getSublist(0,2));
} ...
I was trying to achieve this and I came up with a custom ComboBox that disables the items I don't want the user to select. Below code shows the custom ComboBox class and how to use it.
public class CustomComboBox<T> extends ComboBox<T> {
private ArrayList<T> disabledItems = new ArrayList<T>();
public CustomComboBox() {
super();
setup();
}
public CustomComboBox(ObservableList<T> list) {
super(list);
setup();
}
private void setup() {
SingleSelectionModel<T> model = new SingleSelectionModel<T>() {
#Override
public void select(T item) {
if (disabledItems.contains(item)) {
return;
}
super.select(item);
}
#Override
public void select(int index) {
T item = getItems().get(index);
if (disabledItems.contains(item)) {
return;
}
super.select(index);
}
#Override
protected int getItemCount() {
return getItems().size();
}
#Override
protected T getModelItem(int index) {
return getItems().get(index);
}
};
Callback<ListView<T>, ListCell<T>> callback = new Callback<ListView<T>, ListCell<T>>() {
#Override
public ListCell<T> call(ListView<T> param) {
final ListCell<T> cell = new ListCell<T>() {
#Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item.toString());
if (disabledItems.contains(item)) {
setTextFill(Color.LIGHTGRAY);
setDisable(true);
}
} else {
setText(null);
}
}
};
return cell;
}
};
setSelectionModel(model);
setCellFactory(callback);
}
public void setDisabledItems(T... items) {
for (int i = 0; i < items.length; i++) {
disabledItems.add(items[i]);
}
}
}
Then add the items to disable to the ComboBox:
#FXML private CustomComboBox<String> customComboBox;
...
customComboBox.setDisabledItems("Item 2", "Item 3");
And change the class in the fxml file:
<views.custom.CustomComboBox ... />
I had the same issue and I think that the best solution to this problem is to use the
setCellFactory(Callback,ListCell> value) method of ComboBox:
cBox.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
#Override
public ListCell<String> call(ListView<String> param)
{
return new ListCell<String>() {
#Override
protected void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if (item != null || !empty)
{
this.setText(item);
this.setDisable(true); //or false
}
}
};
}
});
and if you want a custon ButtonCel you need to use the setButtonCell(ListCell value) Method:
cBox.setButtonCell(new ListCell<String>() {
#Override
protected void updateItem(Stringitem, boolean empty)
{
super.updateItem(item, empty);
if (item != null || !empty)
{
this.setText(item);
this.setDisable(true); //or false
}
}
});
comboBox.setItems(FXCollections.observableArrayList(EnumValues.values()));
comboTipoOperacoes.getItems().remove(4); // remove the item 4 of Enums.