JavaFX connected comboboxes - javafx

I could really use some help.
I am creating application that has two connected comboboxes in a way that if i select productCode in first in second one should be selected productName.
Both combobox textfields are filtrable for search purposes.
I have set setCellFactories like this (for purpose of dropdown list rendering).
cbSifra.setCellFactory((comboBox) -> new ListCell<Product>() {
#Override
protected void updateItem(Product product, boolean empty) {
super.updateItem(product, empty);
if (product == null || empty) {
setText(null);
} else {
setText(product.getProductCode());
}
}
});
cbNaziv.setCellFactory((comboBox) -> new ListCell<Product>() {
#Override
protected void updateItem(Product product, boolean empty) {
super.updateItem(product, empty);
if (product == null || empty) {
setText(null);
} else {
setText(product.getProductName());
}
}
});
Both comboboxes implement converters to show data into combobox when selected.
cbNaziv.setConverter(new StringConverter<Product>() {
#Override
public String toString(Product product) {
if (product == null) {
return null;
} else {
return product.productNameProperty().get();
}
}
#Override
public Product fromString(String productString)
{
return cbNaziv.getItems().stream().filter(item->productString.equals(item.getProductName())).findFirst().orElse(null);
}
});
cbSifra.setConverter(new StringConverter<Product>() {
#Override
public String toString(Product product) {
if (product == null) {
return null;
} else {
return product.productCodeProperty().get();
}
}
#Override
public Product fromString(String productString)
{
return cbSifra.getItems().stream().filter(item ->productString.equals(item.getProductCode())).findAny().orElse(null);
}
});
Filtering of dropdown list is done using Listener on textProperty() like this:
cbNaziv.getEditor().textProperty().addListener((obs, oldValue, newValue) -> {
cbNaziv.show();
final TextField editor = cbNaziv.getEditor();
final Product selected = cbNaziv.getSelectionModel().getSelectedItem();
/*
This needs run on the GUI thread to avoid the error described
here: https://bugs.openjdk.java.net/browse/JDK-8081700.
*/
Platform.runLater(() -> {
/*
If the no item in the list is selected or the selected item
isn't equal to the current input, we refilter the list.
*/
if (selected == null || !selected.equals(editor.getText())) {
filteredProductList.setPredicate(item -> {
// We return true for any items that contains the
// same letters as the input. We use toUpperCase to
// avoid case sensitivity.
if (item.getProductName().toUpperCase().contains(newValue.toUpperCase())) {
return true;
} else {
return false;
}
});
}
});
});
cbSifra.getEditor().textProperty().addListener((obs, oldValue, newValue) -> {
cbSifra.show(); // Is used to open dropdown list as i start typing
final TextField editor = cbSifra.getEditor();
final Product selected = cbSifra.getSelectionModel().getSelectedItem();
/*
This needs run on the GUI thread to avoid the error described
here: https://bugs.openjdk.java.net/browse/JDK-8081700.
*/
Platform.runLater(() -> {
/*
If the no item in the list is selected or the selected item
isn't equal to the current input, we refilter the list.
*/
if (selected == null || !selected.equals(editor.getText())) {
filteredProductList.setPredicate(item -> {
// We return true for any items that contains the
// same letters as the input. We use toUpperCase to
// avoid case sensitivity.
if (item.getProductCode().toUpperCase().contains(newValue.toUpperCase())) {
return true;
} else {
return false;
}
});
}
});
});
I have valueProperty Listeners to check if value is selected and to fill some textFields to their values or set them to null.
cbSifra.valueProperty().addListener(new ChangeListener<Product>() {
#Override
public void changed(ObservableValue<? extends Product> observable, Product oldValue, Product newValue) {
if (cbSifra.getValue() == null || cbSifra.getValue().getProductName().isEmpty())
{
cbNaziv.getSelectionModel().clearSelection();
tfMpCijena.setText(null);
tfPopust.setText(null);
} else {
cbNaziv.setValue(cbSifra.getValue());
cbSifra.setValue(cbNaziv.getValue());
cbNaziv.hide();
tfMpCijena.setText(cbSifra.getValue().getProductRetailPrice().toString());
tfPopust.setText("0");
}
}
});
cbNaziv.valueProperty().addListener(new ChangeListener<Product>() {
#Override
public void changed(ObservableValue<? extends Product> observable, Product oldValue, Product newValue) {
if (cbNaziv.getValue() == null || cbNaziv.getValue().getProductName().isEmpty())
{
cbSifra.getSelectionModel().clearSelection();
tfMpCijena.setText(null);
tfPopust.setText(null);
} else {
cbSifra.setValue(cbNaziv.getValue());
cbSifra.hide();
tfMpCijena.setText(cbNaziv.getValue().getProductRetailPrice().toString());
tfPopust.setText("0");
}
}
});
The problems are :
when i start typing something into any combobox it filters ok and
when i select item from dropdown list it fills second combobox but
first combobox editor gets focus again and displays dropdown
again
when i delete entry from combobox it deletes ok but the other
combobox value remains (it isnt deleted)
If you can help me i would really appreciate it.
Thanks in advance.

Assuming that both ComboBoxes are of type Product, you can use bidirectional binding to ensure that the values for both ComboBoxes always point to the same product.
cbNaziv.valueProperty().bindBidirectional(cbSifra.valueProperty());
Using this should allow you to remove your change listeners and will hopefully fix some of the issues you were having.

Related

Add buttons to the row currently being edited

I have tried searching both google and stackoverflow for answer to my question but I could not find any.
I have a program that adds/reads data from a database and to a tableview. I want to be able to edit the previously entered data from the tableview by adding two buttons (Save and abort) to the current row being edited.
I am having some troubles understanding the routines like Callback etc. But I have managed to get a column with two buttons to appear when I start the edit. But I get buttons on every column I just want the buttons on the currently
selected row. Also I don't really understand how to get the current object from the save-button to be able to save it.
And also how the abort-button should work to cancel all changes.
public void setUpTableView() {
columnAnkomstdatum.setCellValueFactory(new PropertyValueFactory<>("arrivalDate"));
columnSupplier.setCellValueFactory(new PropertyValueFactory<>("supplier"));
columnRadiopharmaceutical.setCellValueFactory(new PropertyValueFactory<>("radiopharmaceutical"));
columnActivity.setCellValueFactory(new PropertyValueFactory<>("startActivity"));
columnCalibrationdate.setCellValueFactory(new PropertyValueFactory<>("startDate"));
columnBatchNumber.setCellValueFactory(new PropertyValueFactory<>("batchNumber"));
columnContaminationControl.setCellValueFactory(new PropertyValueFactory<>("contaminationControll"));
columnRoom.setCellValueFactory(new PropertyValueFactory<>("room"));
columnUser.setCellValueFactory(new PropertyValueFactory<>("user"));
tableview.setEditable(true);
columnSupplier.setEditable(true);
columnSupplier.setCellFactory(ComboBoxTableCell.forTableColumn(supplierList));
columnSupplier.setOnEditCommit(t -> {
ArrayList<Radiopharmaceutical> radioListfromSupplier = new RadiopharmaceuticalDao().getRadiopharmaceuticalsBySupplierName(t.getNewValue().getSupplierName());
radioList = FXCollections.observableArrayList(radioListfromSupplier);
t.getRowValue().setSupplier(t.getNewValue());
columnRadiopharmaceutical.setCellFactory(ComboBoxTableCell.forTableColumn(radioList));
if(tableview.getColumns().size() <= 9) {
addButtonsToTable();
}
});
}
private void addButtonsToTable() {
TableColumn<RegRadio, Void> editRow = new TableColumn<>("Edit");
tableview.getColumns().add(editRow);
Callback<TableColumn<RegRadio, Void>, TableCell<RegRadio, Void>> cellFactory = new Callback<TableColumn<RegRadio,Void>, TableCell<RegRadio,Void>>() {
#Override
public TableCell<RegRadio, Void> call(final TableColumn<RegRadio, Void> param) {
final TableCell<RegRadio, Void> cell = new TableCell<RegRadio, Void>() {
private final Button btnSave = new Button("Save");
private final Button btnAbort = new Button("Avbryt");
{
btnSave.setOnAction((ActionEvent event) -> {
RegRadio rr = getTableView().getItems().get(getIndex());
System.out.println("Saved");
});
}
{
btnAbort.setOnAction((ActionEvent event) -> {
System.out.println("Abort");
});
}
#Override
public void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
HBox pane = new HBox(btnSave, btnAbort);
setGraphic(pane);
}
}
};
return cell;
}
};
editRow.setCellFactory(cellFactory);
tableview.getColumns().add(editRow);
}
In your updateItem callback you can check if the cell is in the selected row in order to decide if you should show the buttons or not. Additionally you also need a flag to check if the user is editing. Something like this:
#Override
public void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
var selectedCells = tableview.getSelectionModel().getSelectedCells();
if (empty || !isEditing || selectedCells.isEmpty || getTableRow().getIndex() != selectedCells.get(0).getRow()) {
setGraphic(null);
} else {
HBox pane = new HBox(btnSave, btnAbort);
setGraphic(pane);
}
}
Another approach would be to change the visibility of the buttons depending on whether the cell is in the selected row:
#Override
public void updateItem(Void item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
} else {
HBox pane = new HBox(btnSave, btnAbort);
var selectedCells = tableview.getSelectionModel().getSelectedCells();
pane.setVisible(!selectedCells.isEmpty() && getTableRow().getIndex() == selectedCells.get(0).getRow())
setGraphic(pane);
}
}
(I haven't compiled and tested these code samples)

Cell dependent appearance of cell in JavaFX

How can I change the appearance of a cell based on the status of another cell in the same column of a tableView in JavaFX.
colHidden.setCellValueFactory(param -> {
return param.getValue().hiddenProperty();
});
colHidden.setCellFactory(tc -> new CheckBoxTableCell<>());
colLabel.setCellFactory(...);
I have a cell which contains CheckBoxTableCell<>(). When I select this checkbox the content of the cell colLabel should be replaced by asterisks.
You can do something like this. The basic idea here is to "remember" the property corresponding to the check box in the same row, assuming the cell is not empty, and add a listener to it that updates the text. Then remove the listener from the previous property whenever the cell updates.
colLabel.setCellFactory(col -> new TableCell<RowType, ColumnType>() {
private ObservableValue<Boolean> hiddenProperty ;
ChangeListener<Boolean> listener = (obs, wasHidden, isNowHidden) -> updateText(isNowHidden);
#Override
protected void updateItem(ColumnType item, boolean empty) {
super.updateItem(item, empty);
if (hiddenProperty != null) {
hiddenProperty.removeListener(listener);
}
if (empty) {
setText(null);
hiddenProperty = null ;
} else {
hiddenProperty = getTableView().getItems().get(getIndex()).hiddenProperty();
hiddenProperty.addListener(listener);
updateText(hiddenProperty.get());
}
}
private void updateText(boolean hidden) {
if (hidden) {
setText("********");
} else {
setText(getItem().toString()); // or other format for string, etc
}
}
}
Replace RowType and ColumnType with the actual types used by colLabel (i.e. this assumes you have TableColumn<RowType, ColumnType> colLabel ;).

JavaFX - how to set CellFactory for specific TreeItem

I have a TreeView, with many TreeItem. what i want to do is that i enable modification for the selected TreeItem and disable it for others.
To get all the TreeView to be modifiable i use :
syTree.setEditable(true);
syTree.setCellFactory(TextFieldTreeCell.forTreeView());
}
syTree.setOnEditCommit(new EventHandler<TreeView.EditEvent<String>>() {
#Override
public void handle(TreeView.EditEvent<String> t) {
syTree.getRoot().getChildren().set(syTree.getRow(t.getTreeItem()), new TreeItem<String>(t.getNewValue()));
System.out.println("setOnEditCommit");
//}
}
});
syTree.setOnEditCancel(new EventHandler<TreeView.EditEvent<String>>() {
#Override
public void handle(TreeView.EditEvent<String> t) {
System.out.println("setOnEditCancel");
}
});
This line just change all the TreeItems to TextField when trying to modify :
syTree.setCellFactory(TextFieldTreeCell.forTreeView());
How to do it for a specific TreeItem ?
Any help please ?
I think you are saying you want some cells to be editable, and some not to be editable, depending on some condition on the TreeItem they are displaying. If so, it's possible, you just need to do a little more work with your cell factory:
Callback<TreeView<String>, TreeCell<String>> defaultCellFactory = TextFieldTreeCell.forTreeView();
syTree.setCellFactory((TreeView<String> tv) -> {
TreeCell<String> cell = defaultCellFactory.call(tv);
cell.treeItemProperty().addListener((obs, oldTreeItem, newTreeItem) -> {
if (newTreeItem == null) {
cell.setEditable(false);
} else if ( /* newTreeItem should be editable */) {
cell.setEditable(true);
} else {
cell.setEditable(false);
}
});
return cell ;
});

JavaFX8 Style not immediately updating

I am trying to change the color of the table rows when I set a boolean.
So I have this code:
boolean searchmode = false;
....
columns.forEach(c -> c.setCellFactory(column -> {
return new TableCell<ShowableInWarenkorb, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(empty ? null : item);
if (searchmode) {
getStyleClass().add("searchmode");
} else{
getStyleClass().remove("searchmode");
}
}
};
}));
This CSS:
.searchmode {
-fx-background-color: rgba(153,153,153,0.3);
})
And then I switch searchmode eventually in my code before I am updating the table contents.
But the color does not change immediatley, sometimes I have to click a little bit around before it changes, how can I trigger it manually?
From your code, it looks like you want to apply this to all cells in the table. You can do this without a cell factory at all (though you may need one for other purposes).
Do
PseudoClass searchmodePseudoClass = PseudoClass.getPseudoClass("searchmode");
and then when you change the value of searchmode, do
table.pseudoClassStateChanged(searchmode);
In your css, do
.table-view:searchmode .table-cell {
-fx-background-color: rgba(153,153,153,0.3);
}
If you want to "automate" the update to the pseudoclass state, use a boolean property and add a listener:
private final BooleanProperty searchmode = new SimpleBooleanProperty(false);
public final boolean isSearchmode() {
return searchmodeProperty().get();
}
public final void setSearchmode(boolean searchmode) {
searchmodeProperty().set(searchmode);
}
public BooleanProperty searchmodeProperty() {
return searchmode ;
}
Then if you add the listener
searchmode.addListener((obs, wasSearchmode, isNowSearchmode) ->
table.pseudoClassStateChanged(searchmodePseudoClass, isNowSearchmode));
everything will be wired automatically so the table changes whenever you call setSearchmode(...).

TextField inside TableView gone after scroll JavaFX

I have a table view and inside it, there is one column filled with TextField.
There is no problem when I have few data and my table do not have scroll bar, all TextFields appears.
The problem is, when I scroll down my table and then goes up again, some TextFields are missing.
Here is my code for the column filled with TextField:
purchaseQtyCol.setCellFactory(
new Callback<TableColumn< TransactionModel, TextField>, TableCell< TransactionModel, TextField>>() {
#Override
public TableCell< TransactionModel, TextField> call(final TableColumn< TransactionModel, TextField> p) {
TableCell<TransactionModel, TextField> cell = new TableCell<TransactionModel, TextField>() {
#Override
public void updateItem(TextField item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
/**
* for(CheckBox cbb : canceledCB) {
* if(item.equals(cbb))
* System.out.println("aa" +
* this.indexProperty().getValue() + " " +
* item.isSelected() ); }*
*/
this.setGraphic(item);
}
}
};
cell.setAlignment(Pos.CENTER);
return cell;
}
});
purchaseQtyCol.setCellValueFactory(new Callback<CellDataFeatures<TransactionModel, TextField>, ObservableValue<TextField>>() {
#Override
public ObservableValue<TextField> call(final CellDataFeatures<TransactionModel, TextField> p) {
System.out.println("new textfield");
final TextField qtyField = new TextField() {
#Override
public void replaceText(int start, int end, String text) {
if (text.matches("[0-9]") || text.equals("")) {
super.replaceText(start, end, text);
if (this.getText().isEmpty()) {
p.getValue().setPurchaseQty(0);
p.getValue().setTotalPrice(0);
} else {
p.getValue().setPurchaseQty(Integer.parseInt(this.getText()));
p.getValue().setTotalPrice(p.getValue().purchaseQtyProperty().intValue() * p.getValue().basePriceProperty().intValue());
}
recountTotals();
}
}
#Override
public void replaceSelection(String text) {
if (text.matches("[0-9]") || text.equals("")) {
super.replaceSelection(text);
if (this.getText().isEmpty()) {
p.getValue().setPurchaseQty(0);
p.getValue().setTotalPrice(0);
} else {
p.getValue().setPurchaseQty(Integer.parseInt(this.getText()));
p.getValue().setTotalPrice(p.getValue().purchaseQtyProperty().intValue() * p.getValue().basePriceProperty().intValue());
}
recountTotals();
}
}
};
qtyField.setText("" + p.getValue().purchaseQtyProperty().getValue());
qtyField.setAlignment(Pos.CENTER_RIGHT);
return new SimpleObjectProperty(qtyField);
}
});
I really appreciate helps from you guys.
Regards,
Chrisma Andhika
Chrisma! The problem you face is a famous issue for JavaFX about updating items in tableview. There is already some workaround about it, for example here. The solution was to trigger tableview's internal update mechanism by
tableview.getColumns().get(0).setVisible(false);
tableview.getColumns().get(0).setVisible(true);
But as far as I could understand this solution affected only changes of data, not the style of tableview's nodes.
I also used
#Override
public void updateItem(Object item, boolean empty)
with my implementation, but it was not enough, because when there were too many rows in a table and scroll bar appeared the updating of rows became an absolute mess.
What I needed is to make highlighted all visible rows satifying some criteria by using css. I achieved that in the following way:
Callback<TableView<Person>, TableRow<Person>> callBack =
new Callback<TableView<Person>, TableRow<Person>>() {
#Override
public TableRow<Person> call(TableView<Person> tableView) {
final TableRow<Person> row = new TableRow<Person>() {
#Override
public void updateItem(Person item, boolean empty) {
super.updateItem(item, empty);
if(!empty && item.getFirstName() == "John") {
getStyleClass().add("john");
}
}
};
row.visibleProperty().addListener(new ChangeListener() {
#Override
public void changed(ObservableValue observableValue, Object t, Object t1) {
Boolean oldValue = (Boolean)t;
Boolean newValue = (Boolean)t1;
if(oldValue && !newValue) {
row.getStyleClass().remove("john");
}
if(!oldValue && newValue) {
if(row.getItem().getFirstName() == "John")
row.getStyleClass().add("john");
}
}
});
return row;
}
};
tableview.setRowFactory(callBack);
The css file should contain the following lines:
.john {
-fx-background-color: forestgreen;
-fx-text-fill: white;
-fx-border-style: solid;
}
Of course you may choose different styling of rows.
The Person class should contain
public SimpleStringProperty firstNameProperty() {
return firstName;
}
As you can see I added the listener to the VisibleProperty of a row, where I control the css behavior of each row. Maybe this idea could help to solve data update in tableview in some cases, not just rows styling.
I should aslo add that I started receiving the NullPointerException in the public void changed method in the code above, although it doesn't affect the result of my programm.
I hope it will help you, Chrisma! And others as well!
I just had that problem with checkboxes. They randomly dissapeared when I scrolled my big table rapidly. I solved it just by deleting this line in my cellFactory:
#Override
public void updateItem(Boolean item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
// setGraphic(null); (This line causes weird behaviour in scrolling, delete it)
} else {
checkBox.setSelected(item);
}
}
Well, I also had the same problem, apparently only draw the visible text field, when we use the scroll does not appear, because only drawing the scene but not the textinputcontrol , so my solution is to capture the scroll event and when you use it to resize the textfield and return it to its original size, thus forcing you to repaint the object, now appear with textinputcontrol.forcing repaint the object, now appear with textinputcontrol.
tableview.addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
#Override
public void handle(ScrollEvent scrollEvent) {
System.out.println("Scrolled.");
for(ObservableList<TextField> i:data)
{
for(TextField j: i)
{
j.setPrefSize(141, 31);
j.setPrefSize(140, 30);
}
}
}
});

Resources