Table cell combobox - action not performed - javafx

I'm applying the below cell factory to a column.
targetEnviroment.setCellFactory(new Callback<TableColumn<DevWorkTabBench, String>, TableCell<DevWorkTabBench, String>>() {
#Override
public TableCell<DevWorkTabBench, String> call(TableColumn<DevWorkTabBench, String> param) {
TableCell<DevWorkTabBench, String> cell = new TableCell<DevWorkTabBench, String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
String status = null;
try {
status = getTableView().getItems().get(getIndex()).getObjectStatus();
} catch (IndexOutOfBoundsException ex) {
status = "";
}
if (status.equalsIgnoreCase("ReadyForDeployment")) {
ComboBox<String> comboBox = new ComboBox(environmentList);
comboBox.valueProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
commitEdit(newValue);
}
});
comboBox.setOnShown(new EventHandler<Event>() {
#Override
public void handle(Event event) {
getTableView().edit(getIndex(), getTableColumn());
getTableView().getSelectionModel().select(getIndex());
}
});
comboBox.setValue(item);
setGraphic(comboBox);
} else {
setGraphic(null);
}
if (empty) {
setGraphic(null);
}
}
};
return cell;
}
});
When I change the status to the mentioned status, I get the look of ComboBox in that particular cell but the drop down does not occur. Even after multiple clicks no action seems to be performed on the combobox. I do not get any exception other than the handled one. Other columns are editable and performing task as expected.
I have no idea what is wrong here. Can anyone please help me.

Since you are always displaying the combo box in the (non-empty) cells, you don't really need to go into "editing" mode as the standard TextFieldTableCell etc does. Your implementation is more similar to the CheckBoxTableCell, which essentially bypasses the editing mechanism. From the documentation for that class:
Note that the CheckBoxTableCell renders the CheckBox 'live', meaning
that the CheckBox is always interactive and can be directly toggled by
the user. This means that it is not necessary that the cell enter its
editing state (usually by the user double-clicking on the cell). A
side-effect of this is that the usual editing callbacks (such as on
edit commit) will not be called. If you want to be notified of
changes, it is recommended to directly observe the boolean properties
that are manipulated by the CheckBox.
So your cell implementation behaves similarly: don't invoke edit(...) (which I think is messing things up) and don't rely on the commitEdit(...), cancelEdit() etc methods (which won't work as you're not in editing state), but just update the model class directly.
I can't test, since there isn't a MCVE to work from, so this might not work directly, but it should be enough to get you started toward something that will work.
targetEnviroment.setCellFactory(new Callback<TableColumn<DevWorkTabBench, String>, TableCell<DevWorkTabBench, String>>() {
#Override
public TableCell<DevWorkTabBench, String> call(TableColumn<DevWorkTabBench, String> param) {
TableCell<DevWorkTabBench, String> cell = new TableCell<DevWorkTabBench, String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null) ;
} else {
String status = getTableView().getItems().get(getIndex()).getObjectStatus();
if (status.equalsIgnoreCase("ReadyForDeployment")) {
ComboBox<String> comboBox = new ComboBox(environmentList);
comboBox.valueProperty().addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
//commitEdit(newValue);
getTableView().getItems().get(getIndex()).setObjectStatus(newValue);
}
});
comboBox.setValue(item);
setGraphic(comboBox);
} else {
setGraphic(null);
}
}
}
};
return cell;
}
});

Related

How to make TextFieldTableCell conditional on model property?

I have a TableView with an editable TextFieldTableCell that I want to restrict to be available based on a BooleanProperty of my model object.
For example, textField.disableProperty().bind(item.editableProperty().not())
Currently, I have the basic implementation from the Oracle docs:
colComment.setCellFactory(TextFieldTableCell.forTableColumn());
colComment.setOnEditCommit(event -> event.getTableView().getItems().get(
event.getTablePosition().getRow()).setComment(
event.getNewValue())
);
This obviously does not allow much flexibility. The desire is to check the item's editableProperty and if it is true, display the TextFieldTableCell and bind it to the item's commentProperty.
If that property is false, the cell should simply display the value of the commentProperty.
I have not worked with editable TableViews in the past so I am a bit lost.
I have tried to hack out a workaround with manually setting the graphic myself, but that just does nothing with the cell:
colComment.setCellFactory(cell -> new TableCell<LogEntry, String>() {
final TextField txtComment = new TextField();
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setGraphic(null);
} else {
LogEntry logEntry = (LogEntry) getTableRow().getItem();
if (logEntry.isEditable()) {
txtComment.textProperty().bindBidirectional(logEntry.commentProperty());
setGraphic(txtComment);
} else {
setText(item);
}
}
}
});
The basic approach is to disallow cell's editing based on a condition. TextFieldTableCell has no direct support for such, but can be extended just as any other type of cell. Options are
override startEdit to do nothing if the condition is not met
bind the cell's editability property to a condition of the rowItem
The most simple is the first (the latter is a bit more involved, due to requiring updates when parent TableRow and its item changes). A quick example (all boiler-plate except the cell ;):
public class TableCellConditionalEditable extends Application {
/**
* Cell with custom condition to prevent editing.
*/
public static class ConditionalEditableCell extends TextFieldTableCell<ConditionalWritable, String> {
public ConditionalEditableCell() {
super(new DefaultStringConverter());
}
/**
* Overridden to do nothing if rowItem-related condition not met.
*/
#Override
public void startEdit() {
if (!isConditionalEditable()) return;
super.startEdit();
}
private boolean isConditionalEditable() {
if (getTableRow() == null || getTableRow().getItem() == null || isEmpty()) return false;
return getTableRow().getItem().writableProperty().get();
}
}
private Parent createContent() {
TableView<ConditionalWritable> table = new TableView<>(ConditionalWritable.conditionalWritables());
TableColumn<ConditionalWritable, String> text = new TableColumn<>("Text");
text.setCellValueFactory(cc -> cc.getValue().textProperty());
TableColumn<ConditionalWritable, Boolean> writable = new TableColumn<>("Writable");
writable.setCellValueFactory(cc -> cc.getValue().writableProperty());
table.getColumns().addAll(text, writable);
table.setEditable(true);
text.setCellFactory(cc -> new ConditionalEditableCell());
BorderPane content = new BorderPane(table);
return content;
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.show();
}
public static void main(String[] args) {
launch(args);
}
public static class ConditionalWritable {
private SimpleStringProperty text;
private SimpleBooleanProperty writable;
public ConditionalWritable(String text, boolean writable) {
this.text = new SimpleStringProperty(text);
this.writable = new SimpleBooleanProperty(writable);
}
public StringProperty textProperty() {
return text;
}
public BooleanProperty writableProperty() {
return writable;
}
public static ObservableList<ConditionalWritable> conditionalWritables() {
return FXCollections.observableArrayList(
new ConditionalWritable("some data", false),
new ConditionalWritable("other data", true),
new ConditionalWritable("nothing important", true)
);
}
}
}

Bind CheckBoxTableCell to BooleanBinding

I want to bind a CheckBox in a TableViewCell to a BooleanBinding. The following sample consists of a TableView with a column name and isEffectiveRequired. The checkbox in the column is bound to the Expression:
isRequired.or(name.isEqualTo("X"))
So an item is "effectivly required" when the item in the row is required OR the name is an X, then the expression should be true.
Unfortunately the CheckBox does not reflect the change. For debugging I added a textfield, showing the nameProperty, requiredProperty and the computed effectiveRequiredProperty.
Interestingly when returning just the isRequiredProperty instead of the binding the checkbox works.
public ObservableBooleanValue effectiveRequiredProperty() {
// Bindings with this work:
// return isRequired;
// with this not
return isRequired.or(name.isEqualTo(SPECIAL_STRING));
}
So what is the difference between a Property and a ObservableValue in regard to a CheckBox?
public class TableCellCBBinding extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
private void init(Stage primaryStage) {
primaryStage.setScene(new Scene(buildContent()));
}
private Parent buildContent() {
TableView<ViewModel> tableView = new TableView<>();
tableView.setItems(sampleEntries());
tableView.setEditable(true);
tableView.getColumns().add(buildRequiredColumn());
tableView.getColumns().add(buildNameColumn());
// Add a Textfield to show the values for the first item
// As soon as the name is set to "X", the effectiveRequiredProperty should evaluate to true and the CheckBox should reflect this but it does not
TextField text = new TextField();
ViewModel firstItem = tableView.getItems().get(0);
text.textProperty()
.bind(Bindings.format("%s | %s | %s", firstItem.nameProperty(), firstItem.isRequiredProperty(), firstItem.effectiveRequiredProperty()));
return new HBox(text, tableView);
}
private TableColumn<ViewModel, String> buildNameColumn() {
TableColumn<ViewModel, String> nameColumn = new TableColumn<>("Name");
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setEditable(true);
return nameColumn;
}
private TableColumn<ViewModel, Boolean> buildRequiredColumn() {
TableColumn<ViewModel, Boolean> requiredColumn = new TableColumn<>("isEffectiveRequired");
requiredColumn.setMinWidth(50);
// This is should bind my BindingExpression from to ViewModel to the CheckBox
requiredColumn.setCellValueFactory( p -> p.getValue().effectiveRequiredProperty());
requiredColumn.setCellFactory( CheckBoxTableCell.forTableColumn(requiredColumn));
return requiredColumn;
}
private ObservableList<ViewModel> sampleEntries() {
return FXCollections.observableArrayList(
new ViewModel(false, "A"),
new ViewModel(true, "B"),
new ViewModel(false, "C"),
new ViewModel(true, "D"),
new ViewModel(false, "E"));
}
public static class ViewModel {
public static final String SPECIAL_STRING = "X";
private final StringProperty name;
private final BooleanProperty isRequired;
public ViewModel(boolean isRequired, String name) {
this.name = new SimpleStringProperty(this, "name", name);
this.isRequired = new SimpleBooleanProperty(this, "isRequired", isRequired);
this.name.addListener((observable, oldValue, newValue) -> System.out.println(newValue));
}
public StringProperty nameProperty() {return name;}
public final String getName(){return name.get();}
public final void setName(String value){
name.set(value);}
public boolean isRequired() {
return isRequired.get();
}
public BooleanProperty isRequiredProperty() {
return isRequired;
}
public void setRequired(final boolean required) {
this.isRequired.set(required);
}
public ObservableBooleanValue effectiveRequiredProperty() {
// Bindings with this work:
// return isRequired;
// with this not
return isRequired.or(name.isEqualTo(SPECIAL_STRING));
}
}
}
When typing an X into the name the checkbox in the row should be checked.
When typing an X into the name the checkbox in the row is not checked. It's never checked like it is not bound at all.
CheckBoxXXCells don't live up to their doc when it comes to binding their selected state, f.i. (citing here just for signature, even if not set explicitely):
public final Callback <Integer,​ObservableValue<Boolean>> getSelectedStateCallback()
Returns the Callback that is bound to by the CheckBox shown on screen.
clearly talks about an ObservableValue, so we would expect that it at least shows the selection state.
Actually, the implementation does exactly nothing if it's not a property, the relevant part from its updateItem:
StringConverter<T> c = getConverter();
if (showLabel) {
setText(c.toString(item));
}
setGraphic(checkBox);
if (booleanProperty instanceof BooleanProperty) {
checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
}
ObservableValue<?> obsValue = getSelectedProperty();
if (obsValue instanceof BooleanProperty) {
booleanProperty = (ObservableValue<Boolean>) obsValue;
checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
}
checkBox.disableProperty().bind(Bindings.not(
getTableView().editableProperty().and(
getTableColumn().editableProperty()).and(
editableProperty())
));
To work around, use a custom cell that updates the selected state in its updateItem. With the added quirk that we need to disable the check's firing to really keep the visuals in sync with backing state:
requiredColumn.setCellFactory(cc -> {
TableCell<ViewModel, Boolean> cell = new TableCell<>() {
CheckBox check = new CheckBox() {
#Override
public void fire() {
// do nothing - visualizing read-only property
// could do better, like actually changing the table's
// selection
}
};
{
getStyleClass().add("check-box-table-cell");
check.setOnAction(e -> {
e.consume();
});
}
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
check.setSelected(item);
setGraphic(check);
}
}
};
return cell;
});

JavaFx How to make checkbox visible in tableColumnCell using cellFactory

I want to add checkBox to tableCell but i dont know what to do to make checkBox visible when i use .setCellFactory
I want to achive this with cellFactory function ---> checkBoxVisible
when i want to make use of cellFactory checkBox are not visible
----> wrong effect
window1.setCellFactory(new BooleanColorCellFactory());
window2.setCellFactory(new BooleanColorCellFactory());
This is BooleanColorCellFactory Class
#Override
public TableCell<Server, CheckBox> call(TableColumn<Server, CheckBox> param) {
return new TableCell<Server, CheckBox>(){
#Override
protected void updateItem(CheckBox item, boolean empty) {
super.updateItem(item, empty);
// if(!empty) {
// setVisible(true);
// setEditable(true);
// getChildren().add(item);
// setText(item.toString());
// if(item.isSelected())
// setStyle(" -fx-background-color: Green");
// else
// setStyle(" -fx-background-color: RED");
}
// }
};
}
}
I have tried some things but nothing was working.
What i need to add in BooleanColorCellFactory to make this work ?
UPDATE:
So i was playing around and i manage to get step closer to solution by adding this into BooleanColorCellFactory Class
if(!getChildren().contains(item))
getChildren().add(item);
but it is buggy and dont looks well and its added after i start scrolling(which is weird behavior for me)--> click
You shouldn't put a Node inside the item class unless you really need to. Furthermore never access the children of a Control directly unless you're writing a Skin for this Control.
You should instead add a BooleanProperty to the Server class:
private final BooleanProperty window1 = new SimpleBooleanProperty();
public boolean isWindow1() {
return window1.get();
}
public void setWindow1(boolean value) {
window1.set(value);
}
public BooleanProperty window1Property() {
return window1;
}
TableColumn<Server, Boolean> window1;
Callback<TableColumn<Server, Boolean>, TableCell<Server, Boolean>> factory = new BooleanColorCellFactory();
// cellValueFactory returns property
window1.setCellValueFactory(new PropertyValueFactory<>("window1"));
window1.setCellFactory(factory);
...
window2.setCellFactory(factory);
#Override
public TableCell<Server, Boolean> call(TableColumn<Server, Boolean> param) {
return new TableCell<Server, Boolean>(){
private final CheckBox checkBox = new CheckBox();
{
checkBox.selectedProperty().addListener((o, oldValue, newValue) -> {
// save new value in row item
WritableValue<Boolean> property = (WritableValue<Boolean>) getTableColumn().getCellObservableValue​(getIndex());
property.setValue(newValue);
});
}
#Override
protected void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setGraphic(null);
setStyle(null);
} else {
setGraphic(checkBox);
checkBox.setSelected(item);
setStyle(item ? "-fx-background-color: Green" : "-fx-background-color: RED");
}
}
};
}
Procede accordingly for window2

Apply cell factory only at child level in treetableview

I have a treetableview with 4 level hierarchy. I want to have a column with cell factory as combobox. but this combobox should be visible only at the last child level and not the parent level. Is that possible to achieve?
Edit: I tried the following as per suggestions received.
ObservableList ADM = WSData.getADMObjectMapList();
tbAdmMap.setCellFactory(new Callback<TreeTableColumn<ProjectPlan, String>, TreeTableCell<ProjectPlan, String>>() {
#Override
public TreeTableCell<ProjectPlan, String> call(TreeTableColumn<ProjectPlan, String> param) {
TreeTableCell<ProjectPlan, String> cell = new TreeTableCell<ProjectPlan, String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
System.out.println(getItem());
if (getString().equals("")) {
setGraphic(null);
} else {
ComboBox<String> comboBox = new ComboBox(ADM);
comboBox.setValue(item);
setGraphic(comboBox);
}
}
private String getString() {
return getItem() == null ? "" : getItem();
}
};
return cell;
}
});
It is giving me the proper look as I want but not taking the cell into edit mode. As in the onCommitEdit method is not getting called.

JavaFX TableView delete issue

I'm having some issues with deleting datas from my TableView in JavaFX.
TabeView receives its data from a static ObservableList.
Problem: TableView shows 6 rows at a time. When there are for example 6 data-objects in my ObservableList and i delete 1 of them, the TableView seems to have some update-issues:
Error
As you can see, the red highlighted row seems to be a copy of a still existing value in
my ObservableList. You cannot select this row cause there is of course no existing
value in ObservableList. This behavior keeps appearing after deleting some more values:
enter link description here
All highlights "rows" are not selectable cause there is no corresponding value in my ObservableList. Only after deleting the last value / row in TableView it is refreshed and becomes empty.
My ObservableList ist static
public static ObservableList<ImageData> datas_all;
Deletion is pretty simple
#FXML
private TableView<ImageData> imageTable;
...
#FXML
private void handleDeleteImage()
{
ImageData img_data = imageTable.getSelectionModel().getSelectedItem();
Main.datas_all.remove(img_data);
//if(Main.datas_flickr.contains(img_data))
// Main.datas_flickr.remove(img_data);
//Main.db_adapter.deleteImage(new Integer[]{img_data.getKey()});
//File del_file = new File(img_data.getPath());
//del_file.delete();
}
TableView consists of two rows defined by
#FXML
private TableColumn<ImageData,Image> columnImage;
#FXML
private TableColumn<ImageData,String> columnName;
...
public void redefineTableView()
{
columnImage.setCellFactory(new Callback<TableColumn<ImageData, Image>, TableCell<ImageData, Image>>()
{
#Override
public TableCell<ImageData, Image> call(TableColumn<ImageData, Image> param)
{
final ImageView imgView = new ImageView();
imgView.setFitHeight(100);
imgView.setFitWidth(100);
TableCell<ImageData, Image> cell = new TableCell<ImageData, Image>()
{
public void updateItem(Image image, boolean empty)
{
if(image != null)
imgView.setImage(image);
}
};
cell.setGraphic(imgView);
return cell;
}
});
columnName.setCellFactory(new Callback<TableColumn<ImageData, String>, TableCell<ImageData, String>>()
{
#Override
public TableCell<ImageData, String> call(TableColumn<ImageData, String> param)
{
TableCell<ImageData, String> cell = new TableCell<ImageData, String>()
{
private Text text;
public void updateItem(String string, boolean empty)
{
super.updateItem(string, empty);
if (!isEmpty())
{
text = new Text(string);
text.setWrappingWidth(170);
setGraphic(text);
}
}
};
return cell;
}
});
columnName.setCellValueFactory(new PropertyValueFactory<ImageData, String>("name"));
columnImage.setCellValueFactory(new PropertyValueFactory<ImageData, Image>("image"));
imageTable.setItems(Main.datas_all);
}
I've googled this problem but nobody else seems to have it.
Please help me! =)
This has been asked before, but I can't find it now. The issue is that your updateItem(...) method does not properly handle the case where the image is null (or the cell is empty). This will be exactly the case when you delete an item from the table. You need:
TableCell<ImageData, Image> cell = new TableCell<ImageData, Image>()
{
public void updateItem(Image image, boolean empty)
{
if(image == null) {
setGraphic(null);
} else {
imgView.setImage(image);
setGraphic(imgView);
}
}
};
return cell;
and similarly for the other cell factory.

Resources