Greying out dates in DatePicker with javafx doesn't work as intended - javafx

I only want the user to select dates from today + 10 days in the future. But as you can see it works perfectly for the current month. But in February you can again select the dates although they are after the 10 days I want to be selectable. I only want to make 17.1. - 27.1. Selectable.
This is what i tried: (This doesn't look good but it's enough to show the issue and to you guys to help me.)
#Override
public void start(Stage stage) throws IOException {
DatePicker dpDate = new DatePicker();
dpDate.setDayCellFactory(param -> new DateCell() {
#Override
public void updateItem(LocalDate date, boolean empty) {
super.updateItem(date, empty);
setDisable(empty || date.compareTo(LocalDate.now()) > 10 || date.compareTo(LocalDate.now()) < 0);
}
});
Scene scene = new Scene(dpDate, 320, 240);
stage.setTitle("Hello!");
stage.setScene(scene);
stage.show();
}

LocalDate.compareTo() just guarantees to return a negative, zero, or a positive, depending on whether or not the date is before, equal to, or after the supplied date. There's no guarantee (or expectation) it returns the number of days between the two dates.
Use
dpDate.setDayCellFactory(param -> new DateCell() {
#Override
public void updateItem(LocalDate date, boolean empty) {
super.updateItem(date, empty);
setDisable(empty ||
date.isBefore(LocalDate.now()) ||
date.isAfter(LocalDate.now().plusDays(10))
);
}
});

Related

JavaFX SetText CSS options

I'm using the Jfoenix libary, and I have gotten it to show both the day of the year, and day of the month (after asking here for the first hint). They are both inside the setText line. I'm trying to figure out if I can add CSS to them individually so I can make the Day of the Year appear smaller, in the right corner and maybe a different color. I've googled quite a bit but not getting the answers I'm looking for. Thank you.
endDate.setDayCellFactory(p -> new DateCell() {
#Override
public void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
if (item == null) {
setText("");
} else {
setText(Integer.toString(item.getDayOfYear()) + "\r\n" + Integer.toString(item.getDayOfMonth()));
}
}
});
You cannot style the text in the same Labeled differently, but you can use the graphic property instead to display the date:
DatePicker datePicker = new DatePicker();
datePicker.setDayCellFactory(p -> new DateCell() {
private final Text doy = new Text();
private final Text dom = new Text();
private final VBox graphic = new VBox(doy, dom);
{
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
doy.setFont(Font.font(8));
dom.setFont(Font.font(15));
VBox.setMargin(dom, new Insets(0, 0, 0, 10));
}
#Override
public void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
if (item == null) {
setGraphic(null);
} else {
setGraphic(graphic);
doy.setText(Integer.toString(item.getDayOfYear()));
dom.setText(Integer.toString(item.getDayOfMonth()));
}
}
});
BTW: For java internal string handling usually \r is not required. This is only necessary, if you write the string to a file or use a class that depends on the line separator of the OS; only \n works just fine for the purpose you're using it for here. If you need to use different line separation on different OS, hardcoding \r\n is not a good idea; use System.lineSeparator() instead.

How do I make a generic TableColumn renderer for JavaFX TableView column

In a javaFX view controller, initialize method, I have a TableView column renderer that shows financial amounts, with each cell being rendered with an amount and in red or blue color depending on whether the amount is a debit or credit amount.
#Override
public void initialize(URL url, ResourceBundle rb)
budgetAmountCol.setCellValueFactory(cellData -> cellData.getValue().budgetAmountProperty());
// Custom rendering of the budget amount cells in the column.
budgetAmountCol.setCellFactory((TableColumn<TxnObject, String> column) -> {
return new TableCell<TxnObject, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
}
else {
double amt = Double.parseDouble(item);
if (amt == 0) {
setId("zero-amt");
} else {
if (amt < 0){
item = item.substring(1, item.length());
setId("debit-amt");
} else {
setId("credit-amt");
}
}
}
setText(item);
}
};
});
Because I have several columns that need to be rendered similarily I am trying to avoid copious amounts of source code and turn the above into a single method that can be passed parameters and thus render the columns, something like
private void renderColumn(..) { }
The variables that occupy is table cell in the column are defined in an object definition like this:
public class TxnObject {
..
private final StringProperty budgetAmount;
..
public String getbudgetAmount() {
double d = Double.parseDouble(budgetAmount.get());
setbudgetAmount(MainApp.formatAmount(d));
return budgetAmount.get();
}
public void setbudgetAmount(String budgetAmount) {
this.budgetAmount.set(budgetAmount);
}
public StringProperty budgetAmountProperty() {
return budgetAmount;
}
The requirement is therefore to pass the content of the second line of the code, i.e.
budgetAmountCol.setCellValueFactory(cellData -> cellData.getValue().budgetAmountProperty());
to render column: So at a minimum the method needs the table column and the variable property:
private void renderColumn(TableColumn<TxnObject, String> tcol, StringProperty sprop) {
node.setCellValueFactory(..)
}
But I can’t get the correct call to the method. I have tried the following with the indicated result:
renderColumn(budgetAmountCol, budgetAmountProperty());
syntax error: no method BudgetAmountProperty()
renderColumn(budgetAmountCol, cellData.getValue().budgetAmountProperty());
syntax error: Cannot find symbol CellData
renderColumn(budgetAmountCol, cellData -> cellData.getValue().budgetAmountProperty());
syntax error: StringProperty is not a functional interface
Im finding the syntax and understanding of how to achieve my object rather challenging and would appreciate if I could get some suggestions to try and achieve a solution.
The only way to "pass a method" is by creating a object containing the logic to do this or by using a method reference.
Furthermore even though JavaFX does not enforce those restrictions, id should be unique. Use pseudoclasses instead.
You could use
private static final PseudoClass ZERO = PseudoClass.getPseudoClass("zero-atm");
private static final PseudoClass CREDIT = PseudoClass.getPseudoClass("credit-atm");
private static final PseudoClass DEBIT = PseudoClass.getPseudoClass("debit-atm");
public static <S, T> void renderColumn(TableColumn<S, T> column,
final Callback<? super S, ObservableValue<T>> extractor, final Callback<? super T, String> converter,
final Comparator<T> comparator, final T zero) {
if (extractor == null || comparator == null || zero == null) {
throw new IllegalArgumentException();
}
column.setCellValueFactory(cd -> extractor.call(cd.getValue()));
column.setCellFactory(col -> new TableCell<S, T>() {
#Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
pseudoClassStateChanged(ZERO, false);
pseudoClassStateChanged(CREDIT, false);
pseudoClassStateChanged(DEBIT, false);
if (empty || item == null) {
setText("");
} else {
setText(converter.call(item));
int comparison = comparator.compare(zero, item);
if (comparison == 0) {
pseudoClassStateChanged(ZERO, true);
} else {
pseudoClassStateChanged(comparison < 0 ? CREDIT : DEBIT, true);
}
}
}
});
}
public static <S, T extends Comparable<T>> void renderColumn(TableColumn<S, T> column,
Callback<? super S, ObservableValue<T>> extractor, Callback<? super T, String> converter, T zero) {
renderColumn(column, extractor, converter, Comparator.naturalOrder(), zero);
}
Assuming you change the type of the budget to ObjectProperty<BigDecimal>, the method can be invoked like as shown below. (BigDecimal is a lot easier to work with when it comes to comparing values and doing other mathematical operation in addition to avoiding rounding errors.) Otherwise you could simply hardcode the second type parameter and the other functionality and only keep the first 2 parameters of the method.
renderColumn(column, TxnObject::budgetAmountProperty, (BigDecimal val) -> val.abs().toString(), BigDecimal.ZERO);

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)

JavaFX: setDisable(true) DatePicker problems

I am trying to disable certain dates from a datepicker in javafx (v10) however I keep getting the issue whereby the valid dates seem to change css when moving forwards a month and back. Is this a known bug or can anyone spot a fault with the code?
private void getDayCellFactory(DatePicker datePicker1) {
Callback<DatePicker, DateCell> dayCellFactory = (final DatePicker datePicker) -> new DateCell() {
#Override
public void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
if (item.isAfter(LocalDate.now().plusDays(9))) {
setDisable(true);
}
if (item.isBefore(ChronoLocalDate.from(LocalDate.now()))) {
setDisable(true);
}
}
};
datePicker1.setDayCellFactory(dayCellFactory);
}

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