Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DatePicker does not commit value if focus is changed within ChangeListener

Tags:

java

javafx

The following issue is occurring with JDK 1.8 (8u231), Windows 10

I have a DatePicker setup with a listener that shows an Alert when the value changes. However, as soon as the Alert is displayed, the datePicker.valueProperty() reverts back to its original value.

This does not appear to be "working as designed" and multiple other developers have confirmed the issue does not exist in later versions of Java.

Here is a MCVE to demonstrate:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.DatePicker;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class DatePickerCommit extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        // Simple Interface
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        DatePicker datePicker = new DatePicker();

        // Add listener on DatePicker
        datePicker.valueProperty().addListener((observable, oldValue, newValue) -> {

            if (newValue != null) {

                // Show an Alert
                Alert alert = new Alert(Alert.AlertType.WARNING);
                alert.setContentText("You selected " + newValue);
                alert.show();
                alert.setY(alert.getY()+100);
            }

        });

        root.getChildren().add(datePicker);

        // Show the stage
        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("Sample");
        primaryStage.show();
    }
}

The Alert itself does display the correct date, but the DatePicker valueProperty() reverts back to null, as seen in this screenshot:

screenshot

Using IntelliJ's debugger, I can confirm the datePicker.valueProperty() is set to 'null' as soon as alert.show() (or alert.showAndWait()) is called.

Closest Potential Known Bugs:

I was able to find a few known bugs that seem to be related, but they have all been marked as resolved as of 8u73 (possible regression?):

  • JDK-8097293 : [DatePicker] Does not change to new selected value if modal window is shown in DatePicker-action
  • JDK-8138730 : [ComboBox, DatePicker] Value doesn't update when focus is lost
  • JDK-8136838 : [ComboBox, DatePicker] Value doesn't update when focus is lost
like image 575
Zephyr Avatar asked Dec 09 '19 19:12

Zephyr


2 Answers

I am able to reproduce this issue in the JDK version I am currently working. So on investigation, the root cause is getting focus on to new stage (or losing focus from DatePicker) before the value is committed to textField.

While debugging I noticed the below outcome:

  1. When window focus is lost, it turns of focus on all child nodes.
  2. ComboBoxPopupControl calls a method setTextFromTextFieldIntoComboBoxValue when focus is lost. At this point if you look at the value in the method, the textField's text is empty and the 'value' is null resulting in setting null value to the comboBoxBase(highlighted line).

enter image description here I think we can tweak this in two ways:

Option#1: Taking @Kleopatra solution in another way,i.e, by setting the text just before showing alert. This way we are fooling the ComboBoxPopupControl->setTextFromTextFieldIntoComboBoxValue method that there is a valid value in textField and not let it to reset the value.

Option#2: Wrap the part of the showing alert in Platform.runLater, to process the the alert showing at later point in execution (by that time the commit will be already performed in DatePicker).

This worked for both manually entered dates and dates selected in popup.

Not sure if this work around is suitable for you. Can you give a try?

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.DatePicker;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.time.LocalDate;

public class DatePickerCommit extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        // Simple Interface
        VBox root = new VBox(10);
        root.setAlignment(Pos.CENTER);
        root.setPadding(new Insets(10));

        DatePicker datePicker = new DatePicker();

        // Add listener on DatePicker
        datePicker.valueProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue != null) {
                // Option#1
               datePicker.getEditor().setText(datePicker.getConverter().toString(newValue));
               showAlert(newValue);

               // Option#2
               //Platform.runLater(()->showAlert(newValue));
            }
        });

        root.getChildren().add(datePicker);

        // Show the stage
        primaryStage.setScene(new Scene(root));
        primaryStage.setTitle("Sample");
        primaryStage.show();
    }

    private void showAlert(LocalDate value){
        Alert alert = new Alert(Alert.AlertType.WARNING);
        alert.setContentText("You selected " + value);
        alert.show();
        alert.setY(alert.getY()+100);
    }
}
like image 191
Sai Dandem Avatar answered Nov 11 '22 12:11

Sai Dandem


Basically it's a unwanted side-effect of a bug fix to support commitOnFocusLost on ComboBox and DatePicker that was accidentally (?) reverted by another bug fix to support consistent commit-cancel api on ComboBox et al and Spinner which "forgot" to add the commit-cancel api on DatePicker leading to a regression of the original bug for DatePicker with the benefit of not having the unwanted side-effect :)

To hack around, you can add your own support for commit-on-focusLost similarly to what is done for ComboBox/Spinner, that is by adding a focusListener to the DatePicker and update the editor from the picker's value (beware: not formally tested!)

datePicker.focusedProperty().addListener((src, ov, nv) -> {
    if (!nv) {
        datePicker.getEditor().setText(datePicker.getConverter().toString(datePicker.getValue()));
    }
});
like image 1
kleopatra Avatar answered Nov 11 '22 13:11

kleopatra