Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX datepicker not updating value

Bit of an odd one, I'm using JavaFX 8 and am having some odd behaviour I picked up during my Jubula testing.

I have a datepicker control that is created with the following code:

public DatePicker getDatePicker(DtDate defaultDate, int width){
    DatePicker dtpckr = new DatePicker();
    dtpckr.setMaxWidth(width);
    dtpckr.setMinWidth(width);
    dtpckr.setConverter(new StringConverter<LocalDate>() {
        private DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern("yyyy/MM/dd");
        @Override
        public String toString(LocalDate localDate) {
            if(localDate==null)
                return "";
            return dateTimeFormatter.format(localDate);
        }
        @Override
        public LocalDate fromString(String dateString) {
            if(dateString==null || dateString.trim().isEmpty())
                return null;
            return LocalDate.parse(dateString,dateTimeFormatter);
        }
    });
    dtpckr.setPromptText("yyyy/MM/dd");
    dtpckr.setValue(LocalDate.parse(defaultDate.toString(), DateTimeFormatter.ofPattern("yyyy/MM/dd")));
    return dtpckr;
}

That works fine and the control is created on the form and works as expected. If I use the calendar to pick the date, the value is updated.

Now, for the odd part, if I enter in the datepicker and manually type the date in (E.g. 2017/10/11) and then exit the control by either tabbing or clicking on the next textbox on the form, the datepicker localdate value doesn't get updated. The only method to update the date would be to press enter to force the datepicker to realise a new value is present. This causes another problem as I have the following code:

public void createSetDateAndTimeControls(){
    DtDateAndTime currentDateAndTime;
    try {
        currentDateAndTime = systemstateController.getServerDateTime();
        /*
         * Creating controls to be used on the form
         */
        int width = 150;
        DatePicker dtpckr = getDatePicker(currentDateAndTime.date, width);
        Label lblDate = new Label("Date:");
        Label lblTimeHour = new Label("Hour:");
        Label lblTimeMinute = new Label("Minute:");
        Label lblTimeSecond = new Label("Second:");
        TextField txtfldCurrentSetSecond = new TextField();
        TextField txtfldCurrentSetMinute = new TextField();
        TextField txtfldCurrentSetHour = new TextField();
        Slider sldrHourPicker = getSlider(width, SliderType.Hour, txtfldCurrentSetHour, currentDateAndTime.time);
        Slider sldrMinutePicker = getSlider(width, SliderType.Minute, txtfldCurrentSetMinute, currentDateAndTime.time);
        Slider sldrSecondPicker = getSlider(width, SliderType.Second, txtfldCurrentSetSecond, currentDateAndTime.time);
        /*
         * Adding a grid pane to keep controls in correct place and aligned correctly
         */
        GridPane grdpn = new GridPane();
        grdpn.add(lblDate, 1, 1);
        grdpn.add(dtpckr, 2, 1, 2,1);
        grdpn.add(lblTimeHour, 1, 2);
        grdpn.add(sldrHourPicker, 2, 2, 2 ,1);
        grdpn.add(txtfldCurrentSetHour, 4, 2);
        grdpn.add(lblTimeMinute, 1, 3);
        grdpn.add(sldrMinutePicker, 2, 3, 2, 1);
        grdpn.add(txtfldCurrentSetMinute, 4, 3);
        grdpn.add(lblTimeSecond, 1, 4);
        grdpn.add(sldrSecondPicker, 2, 4, 2, 1);
        grdpn.add(txtfldCurrentSetSecond, 4, 4);
        /*
         * Creating buttons for user to press
         */
        Button bttnOK = new Button("Change date and time");
        bttnOK.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                try {
                    if (checkIfAllDialogHasBeenFilledIn(grdpn)){
                        LocalDate test = dtpckr.getValue();
                        if (systemstateController.oeSetClock(ICrashUtils.setDateAndTime(dtpckr.getValue().getYear(), dtpckr.getValue().getMonthValue(), dtpckr.getValue().getDayOfMonth(),
                                (int)Math.floor(sldrHourPicker.getValue()), (int)Math.floor(sldrMinutePicker.getValue()), (int)Math.floor(sldrSecondPicker.getValue()))).getValue())
                            showOKMessage("Updated", "Date and time was updated successfully");
                        else
                            showErrorMessage("Error", "There was an error updating the date and time");
                    }
                    else
                        showUserCancelledActionPopup();
                } catch (ServerOfflineException | ServerNotBoundException e) {
                    showExceptionErrorMessage(e);
                }
            }
        });
        bttnOK.setDefaultButton(true);
        grdpn.add(bttnOK, 2, 5, 3, 1);
        Button bttnSetNow = new Button("Now");
        bttnSetNow.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                dtpckr.setValue(LocalDate.now());
                LocalTime time = LocalTime.now();
                sldrHourPicker.setValue(time.getHour());
                sldrMinutePicker.setValue(time.getMinute());
                sldrSecondPicker.setValue(time.getSecond());
            }
        });

        grdpn.add(bttnSetNow, 4, 1);
        /*
         * End of creating controls to be used on the form
         */
        anchrpnActivator.getChildren().add(grdpn);
        AnchorPane.setTopAnchor(grdpn, 0.0);
        AnchorPane.setRightAnchor(grdpn, 0.0);
        AnchorPane.setLeftAnchor(grdpn, 0.0);
        AnchorPane.setBottomAnchor(grdpn, 0.0);
    } catch (ServerOfflineException | ServerNotBoundException e) {
        showExceptionErrorMessage(e);
    }
}

Which has the following line:

bttnOK.setDefaultButton(true);

This means that if a user presses enter during the text entry on the date picker control, it fires off the onaction event, before the user has put in data in the other controls, and I don't particularly want that.

So the question is, why doesn't the dtpckr.getValue() change if a user enters text into the box and then leaves the box without pressing enter? I can't find an event to call on user leaving the datepicker box or a property of the datepicker to get the text value of the control and compare it to the value of dtpckr.getValue(). Is there something that I'm missing here?

Let me know if you need more information!

[Edit]

It appears this might be a bug in the current datepicker setup in JavaFX

I might have to remove the default button and put in an enter press in the Jubula test to make this work properly. Unless someone has an alternative?

like image 577
Draken Avatar asked Sep 02 '15 07:09

Draken


4 Answers

The bug log that I added above had the answer. You can access the string value of the textbox via this code:

datePicker.getEditor().getText();

So setting the textbox value can be done via:

datePicker.setValue(datePicker.getConverter()
    .fromString(datePicker.getEditor().getText()));

I'm adding an event to the lost focus event, that will force the datepicker value to be updated


And the working code:

public DatePicker getDatePicker(DtDate defaultDate, int width){
    DatePicker dtpckr = new DatePicker();
    dtpckr.setMaxWidth(width);
    dtpckr.setMinWidth(width);
    dtpckr.setConverter(new StringConverter<LocalDate>() {
        private DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern("yyyy/MM/dd");
        @Override
        public String toString(LocalDate localDate) {
            if(localDate==null)
                return "";
            return dateTimeFormatter.format(localDate);
        }
        @Override
        public LocalDate fromString(String dateString) {
            if(dateString==null || dateString.trim().isEmpty())
                return null;
            try{
                return LocalDate.parse(dateString,dateTimeFormatter);
            }
            catch(Exception e){
                //Bad date value entered
                return null;
            }
        }
    });
    dtpckr.setPromptText("yyyy/MM/dd");
    dtpckr.setValue(LocalDate.parse(defaultDate.toString(), DateTimeFormatter.ofPattern("yyyy/MM/dd")));
    //This deals with the bug located here where the datepicker value is not updated on focus lost
    //https://bugs.openjdk.java.net/browse/JDK-8092295?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
    dtpckr.focusedProperty().addListener(new ChangeListener<Boolean>() {
        @Override
        public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
            if (!newValue){
                dtpckr.setValue(dtpckr.getConverter().fromString(dtpckr.getEditor().getText()));
            }
        }
    });
    return dtpckr;
}
like image 125
Draken Avatar answered Nov 17 '22 07:11

Draken


@Draken's answer needs a little extra. If the user types an invalid date the code will throw a DateTimeParseException. You can emulate the DatePicker's own behaviour thus:

dtpckr.getEditor().focusedProperty().addListener((obj, wasFocused, isFocused)->{
      if (!isFocused) {
         try {
             dtpckr.setValue(dtpckr.getConverter().fromString(dtpckr.getEditor().getText()));
         } catch (DateTimeParseException e) {
                dtpckr.getEditor().setText(dtpckr.getConverter().toString(dtpckr.getValue()));
         }
      }
});
like image 32
coffin dodger Avatar answered Nov 17 '22 08:11

coffin dodger


I recently fixed this issue in the newest JavaFX version: https://github.com/openjdk/jfx/pull/679

So when JavaFX 18-ea+9 will be released you can use it and remove the workaround. :-)

like image 3
Maran23 Avatar answered Nov 17 '22 09:11

Maran23


I had the same problem. Did this trick to solve it:

Date date = DateConverter.toDate(datePicker.getEditor().getText());

where DateConverter.toDate() is a method that converts the string to date.

like image 2
ceklock Avatar answered Nov 17 '22 07:11

ceklock