Add buttons to the row currently being edited - button

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)

Related

Edit JavaFx TableView cell with TextField in different columns

I have TableView which contain 3 columns, Two of them are edit with TextFields to permit user enter data into them but when I retrieve items from the table it return what is not into the TextFields like in image not return the correct numbers in the cells
Table with sample data
this is my code:
Edit I found that the problem in my ObservableList (data) I add action to the TextField to update data with new value but how to make code update different rows because it update the value with the last value in the TextField and get rid of to press ENTER to get the whole value in the TxtField because it ignore the last char for example if enter "30" in the TxtField I get "3" not "30"
productQuantity.setCellFactory(new Callback <TableColumn, TableCell> ()
{
#Override
public TableCell call(TableColumn param)
{
return new TableCell()
{
private TextField txtProductQuantity;
#Override
protected void updateItem(Object item, boolean empty)
{
super.updateItem(item, empty);
if(!empty)
{
setGraphic(getTextField());
}
else
{
setGraphic(null);
}
}
public TextField getTextField()
{
if(txtProductQuantity == null)
{
txtProductQuantity = new TextField();
txtProductQuantity.setAlignment(Pos.BASELINE_RIGHT);
txtPurchaseItemPrice.setOnKeyPressed(e ->
{
data.get(0).setPurchaseItemPrice(txtPurchaseItemPrice.getText());
});
}
return txtProductQuantity;
}
};
}
});
productQuantity.setOnEditCommit(new EventHandler<CellEditEvent<ProductOpeningBalance, String>> ()
{
#Override
public void handle(CellEditEvent<ProductOpeningBalance, String> t)
{
((ProductOpeningBalance)
t.getTableView().getItems().get(t.getTablePosition().getRow())
).setProductQuantity(t.getNewValue());
}
});
the code to retrieve data from the table
size = tableProductOpeningBalance.getItems().size();
for(int i = 0; i < size; i++)
{
System.out.println(tableProductOpeningBalance.getItems().get(i).getPurchaseItemPrice());
}
please help?

JavaFX TableView custom row style on double click

I have a TableView in my JavaFX application.
I would like to style differently row when it is double-clicked on it, and differently when it is single-clicked.
Here is what I achieve:
final PseudoClass doubleClickPseudoClass = PseudoClass.getPseudoClass("new");
setRowFactory(tableView -> {
final TableRow<Bean> row = new TableRow<Bean>();
row.setOnMouseClicked(event -> {
if (event.getClickCount() == 2 && (! row.isEmpty())) {
row.pseudoClassStateChanged(doubleClickPseudoClass, true);
});
return row;
});
However, when the user doubles click on every new row, I want all previously double-clicked rows to be styled without applying "new" class:
row.pseudoClassStateChanged(doubleClickPseudoClass, false);
How can I do that?
Now I have cumulative styled all rows as they are double-clicked.
You shouldn't use TableRows to store the state themselves since new items may be assigned to a TableRow instance. Instead use a property to store the item double-clicked item and use a listener for styling the rows:
final ObjectProperty<Bean> doubleClickedObject = new SimpleObjectProperty<>();
setRowFactory(tableView -> new TableRow<Bean>() {
private void updateStyle() {
pseudoClassStateChanged(doubleClickPseudoClass, !isEmpty() && doubleClickedObject.get() == getItem());
}
private final InvalidationListener listener;
{
listener = o -> updateStyle();
doubleClickedObject.addListener(new WeakInvalidationListener(listener));
setOnMouseClicked(event -> {
if (!isEmpty() && event.getClickCount() == 2) {
doubleClickedObject.set(getItem());
}
});
}
#Override
protected void updateItem(Bean item, boolean empty) {
super.updateItem(item, empty);
updateStyle();
}
});

How to force TableRow repaint

How to force TableRow repaint ?.
Imagine the following scenario: The tableView is updated every 180 milliseconds, but the cell that receives the TableRow style information is not visible, and every time it is upgraded TableRow needs to be repainted. When I use refresh() method, it does not look good, especially with the mouse positioning on the TableView, it blinks and in this case consuming cpu.
myTableView.setRowFactory( new Callback<TableView, TableRow<Line>>() {
#Override
public TableRow call(final TableView p) {
return new TableRow<Line>() {
#Override
public void updateItem(Line item, boolean empty) {
super.updateItem(item, empty);
if(item != null) {
if(item.statusProperty().getValue().equals("BORDER")) {
setStyle("-fx-border-color:green;-fx-border-width:2;-fx-opacity:1;");
}
}
}
};
}
});
Since the style depends on the statusProperty() of the Line, which is observable, you can use a binding:
#Override
public void updateItem(Line item, boolean empty) {
super.updateItem(item, empty);
if(item != null) {
styleProperty().bind(Bindings
.when(item.statusProperty().isEqualTo("BORDER"))
.then("-fx-border-color:green;-fx-border-width:2;-fx-opacity:1;")
.otherwise(""));
} else {
styleProperty().unbind();
setStyle("");
}
}
An alternative way to create the binding, which is probably more convenient if the logic is more complicated, is
#Override
public void updateItem(Line item, boolean empty) {
super.updateItem(item, empty);
if(item != null) {
styleProperty().bind(Bindings.createStringBinding(() -> {
if ("BORDER".equals(item.getStyle())) {
return "-fx-border-color:green;-fx-border-width:2;-fx-opacity:1;" ;
} else {
return "" ;
}
}, item.statusProperty());
} else {
styleProperty().unbind();
setStyle("");
}
}
This way the table row will observe the current item's status property, and automatically update the style if that property changes.
If you really want to make the code cleaner, of course, you should move the styles to an external CSS file. You can create a CSS PseudoClass (or more than one) which you can set and unset on the row:
final PseudoClass borderPC = PseudoClass.getPseudoClass("border");
myTableView.setRowFactory(p -> {
TableRow<Line> row = new TableRow<>();
ChangeListener<String> statusListener = (obs, oldStatus, newStatus) ->
row.pseudoClassStateChanged(borderPC, "BORDER".equals(newStatus)) ;
row.itemProperty().addListener((obs, oldLine, newLine) -> {
if (oldLine != null) {
oldLine.statusProperty().removeListener(statusListener);
}
if (newLine == null) {
row.pseudoClassStateChanged(borderPC, false);
} else {
newLine.statusProperty().addListener(statusListener);
row.pseudoClassStateChanged(borderPC, "BORDER".equals(newLine.getStatus()));
}
};
return row ;
});
Then in your external CSS file, do
.table-row-cell:border {
-fx-border-color:green;
-fx-border-width:2;
-fx-opacity:1;
}
Again, you can easily add more psuedoclasses, more rules to the CSS, and additional tests and pseudoclass updates using this approach.

Colouring table row in JavaFX

This question is related to this. Now I want to colour the row where field value equals to some value.
#FXML
private TableView<FaDeal> tv_mm_view;
#FXML
private TableColumn<FaDeal, String> tc_inst;
tc_inst.setCellValueFactory(cellData -> new SimpleStringProperty(""+cellData.getValue().getInstrumentId()));
tc_inst.setCellFactory(column -> new TableCell<FaDeal, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
} else {
setText(item);
// Style row where balance < 0 with a different color.
TableRow currentRow = getTableRow();
if (item.equals("1070")) {
currentRow.setStyle("-fx-background-color: tomato;");
} else currentRow.setStyle("");
}
}
});
The problem is I don't want to show tc_inst in my table. For this reason I set visible checkbox in SceneBuilder to false. In this case colouring part doesn't work at all. How can hide tc_inst so that colouring works?
Use a row factory, instead of a cell factory, if you want to change the color of the whole row:
tv_mm_view.setRowFactory(tv -> new TableRow<FaDeal>() {
#Override
public void updateItem(FaDeal item, boolean empty) {
super.updateItem(item, empty) ;
if (item == null) {
setStyle("");
} else if (item.getInstrumentId().equals("1070")) {
setStyle("-fx-background-color: tomato;");
} else {
setStyle("");
}
}
});
Note that if the value of instrumentId changes while the row is displayed, then the color will not change automatically with the above code, unless you do some additional work. The simplest way to make that happen would be to construct your items list with an extractor which returned the instrumentIdProperty() (assuming you are using the JavaFX property pattern in FaDeal).

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