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.
Related
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;
});
I have a TreeTableView with couple of columns. One of the column is having ProgressIndicator, which is generated using setCellFactory on TreeTableColumn. This table contains list of files.
User selects a row and click on download button then I am getting selected row by calling getSelectionModel() but the problem is I am getting the value of the row (data) but how I can get the progressIndicator which I injected in the column. So while file download I can update this indicator.
Below is the code:
//Table setup with two columns; file name and progressindicator column, File is my custom class which contains file related info.
#FXML TreeTableView<File> table = new TreeTableView<>();
#FXML TreeTableColumn<File, String> fileCol = new TreeTableColumn<>();
#FXML TreeTableColumn<File, String> progressCol = new TreeTableColumn<>();
//Generating progress indicator with setCellFactory
progressCol.setCellFactory(new Callback<>() {
#Override public TreeTableCell<File, String> call(TreeTableColumn<File, String> p) {
return new TreeTableCell<>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
ProgressIndicator p1 = new ProgressIndicator();
p1.setMaxWidth(20);
p1.setMaxHeight(20);
setGraphic(p1);
}
}
};
}
});
//I have a download button, when it is click below code is executed
download.setOnAction(event -> {
//Here I am getting File object but I also need to get progressindicator which is in progressCol, how I can get it?
File object = table.getSelectionModel().getSelectedItem().getValue();
});
Just create a wrapper class for the File and the ProgressIndicator, and use that for the TreeTableView.
public class FileWrapper {
private File file;
private ProgressIndicator progressIndicator = new ProgressIndicator();
public FileWrapper(File file) {
this.file = file;
}
public File getFile() {
return file;
}
public ProgressIndicator getProgressIndicator() {
return progressIndicator;
}
}
When using DataTables in JavaFx the "type" of the DataTable should represent one row, in your case the File only covers one column.
So your code would look something like this:
#FXML
private TreeTableView<FileWrapper> tableView;
#FXML
private TreeTableColumn<FileWrapper, String> fileCol;
#FXML
private TreeTableColumn<FileWrapper, ProgressIndicator> progressCol;
#FXML
public void initialize() throws IOException {
fileCol.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getValue().getFile().getName()));
progressCol.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().getValue().getProgressIndicator()));
progressCol.setCellFactory(param -> new TreeTableCell<>() {
#Override
protected void updateItem(ProgressIndicator item, boolean empty) {
super.updateItem(item, empty);
setText(null);
if (item == null || empty) {
setGraphic(null);
} else {
setGraphic(item);
}
}
});
And at the download action:
download.setOnAction(event -> {
File object = table.getSelectionModel().getSelectedItem().getValue().getFile();
ProgressIndicator = table.getSelectionModel().getSelectedItem().getValue().getProgressIndicator()
});
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;
}
});
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.
After messing around with Netbeans and Scenebuilder for a while I'm stuck at a problem I can't quite understand. I use a custom cellfactory to bind a doubleclick event to the cells in my tableview. But when I set the cellfactory and a cellValueFactory only the custom cellFactory has an effect.
I'm trying to populate a tableview with data from a number of objects and bind a double click event to the cells of the first column. Populating is not the problem, I just used
idNumber.setCellValueFactory(new PropertyValueFactory<LiveStock, String>("idNumber"));
status.setCellValueFactory(new PropertyValueFactory<LiveStock, String>("status"));
I then googled around to figure out how to bind a doubleclick event to the cells of the table and found javafx, TableView: detect a doubleclick on a cell
amongst others...
I defined a custom cellFactory like this:
Callback<TableColumn<LiveStock, String>, TableCell<LiveStock, String>> cellFactory =
new Callback<TableColumn<LiveStock, String>, TableCell<LiveStock, String>>() {
#Override
public TableCell call(TableColumn p) {
TableCell cell = new TableCell<LiveStock, String>() {};
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() == 2) {
System.out.println("double clicked!");
TableCell c = (TableCell) event.getSource();
System.out.println("Livestock ID: " + c.getId());
}
}
});
return cell;
}
I removed the update and toString methods just to see if they where the reason I ran in to problems.
So I tried
idNumber.setCellFactory(cellFactory);
idNumber.setCellValueFactory(new PropertyValueFactory<LiveStock, String>("idNumber"));
This results in my cells beeing empty, but having the double click binding
any ideas?
My LiveStock class looks like this:
package projekt1.fx;
import javafx.beans.property.SimpleStringProperty;
public class LiveStock {
private final int idNumber;
private final SimpleStringProperty ownerID;
private SimpleStringProperty status;
private double lat;
private double longd;
public LiveStock(int idNumber, String ownerID) {
this.idNumber = idNumber;
this.ownerID = new SimpleStringProperty(ownerID);
this.status = new SimpleStringProperty("ok");
}
public int getIdNumber() {
return this.idNumber;
}
// public void setIdNumber(int number) {
// this.idNumber = number;
// }
public String getOwnerID(){
return ownerID.get();
}
public void setOwnerID(String id){
ownerID.set(id);
}
public String getStatus(){
return status.get();
}
public void setStatus(String st){
status.set(st);
}
}
The cellfactory now looks like this:
Callback<TableColumn<LiveStock, String>, TableCell<LiveStock, String>> cellFactory =
new Callback<TableColumn<LiveStock, String>, TableCell<LiveStock, String>>() {
#Override
public TableCell call(TableColumn p) {
TableCell cell = new TableCell<LiveStock, String>() {
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
// setText("HELLO WORLD!");
setText(empty ? null : getString());
}
};
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() == 2) {
System.out.println("double clicked!");
TableCell c = (TableCell) event.getSource();
System.out.println("Livestock ID: " + c.getId());
togglePopup(null);
}
}
});
return cell;
}
};
Documentation of Cell API says:
Because by far the most common use case for cells is to show text to a
user, this use case is specially optimized for within Cell. This is
done by Cell extending from Labeled. This means that subclasses of
Cell need only set the text property, rather than create a separate
Label and set that within the Cell. ...
The current source code of Cell constructor sets the text to null:
public Cell() {
setText(null);
...
}
The subclass IndexedCell and sub-subclass TableCell, both of them don't set the text of Labeled.
The text is set by default cell factory of TableColumn in source code.
public static final Callback<TableColumn<?,?>, TableCell<?,?>> DEFAULT_CELL_FACTORY = new Callback<TableColumn<?,?>, TableCell<?,?>>() {
#Override public TableCell<?,?> call(TableColumn<?,?> param) {
return new TableCell() {
#Override protected void updateItem(Object item, boolean empty) {
if (item == getItem()) return;
super.updateItem(item, empty);
if (item == null) {
super.setText(null);
super.setGraphic(null);
} else if (item instanceof Node) {
super.setText(null);
super.setGraphic((Node)item);
} else {
super.setText(item.toString());
super.setGraphic(null);
}
}
};
}
};
However by defining your own cell factory that creates new TableCell but does not set the text in its overriden updateItem() method, will be resulting an empty (=null) column cell text. So yes the reason of the problem was removing updateItem method, which calls setText(...) internally.
EDIT:
Specify the generic types explicitly for TableColumns as,
TableColumn<LiveStock, Integer> idNumber = new TableColumn<LiveStock, Integer>("ID No");
This will avoid type mismatches or wrong type castings.
Then the cell factory callback for your use case will be
Callback<TableColumn<LiveStock, Integer>, TableCell<LiveStock, Integer>> cellFactory =
new Callback<TableColumn<LiveStock, Integer>, TableCell<LiveStock, Integer>>() {
public TableCell<LiveStock, Integer> call(TableColumn<LiveStock, Integer> p) {
TableCell<LiveStock, Integer> cell = new TableCell<LiveStock, Integer>() {
#Override
public void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
setText((item == null || empty) ? null : item.toString());
setGraphic(null);
}
};
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if (event.getClickCount() > 1) {
System.out.println("double clicked!");
TableCell c = (TableCell) event.getSource();
System.out.println("Cell text: " + c.getText());
}
}
});
return cell;
}
};
What is changed?
The type of idNumber in LiveStock is int. By defining new TableColumn<LiveStock, Integer> we say this is a column from LiveStock row for its attribute idNumber which has a type int, but the generic types must be a reference type, it cannot be TableCell<LiveStock, int> so we define TableCell<LiveStock, Integer>. The thumb of rule: row item class's attribute type should match the second generic type parameter of TableColumn and due to this the parameter of TableCell also.
getString method is defined in the referenced answer link mentioned by you. But it is just a user defined method, not mandatory to use it.