简体   繁体   中英

Java 8 DatePicker and editable ComboBox behavior change between 8u51 and 8u60

We have searched this forum and the web extensively and have not found anything related to this issue so I felt compelled to post this question here...

We have observed a major behavioral change with regard to the functionality of both the JavaFX8 DatePicker and editable ComboBox elements between the release of Java 8 u51 and u60.

Running under u51 you could enter a date such as 12/30/1970 in a DatePicker and tab to the next UI element and the data would automatically be saved and if the date was bound to an age calculation TextField the age would update. The same for editable ComboBox elements. However running under u60 the user MUST press ENTER after a data change on either the DatePicker or editable ComboBox, otherwise the data is not saved. Tabbing to the next UI element or a mouse click outside of the UI element and the data is lost, replaced by what was previously saved in that element prior to the edit.

So my question is, has anyone else noticed this critical behavioral change and if so feel this is a bug in u60 or a direction Oracle is deliberately taking for some reason?

Finally is there a work around, perhaps in the form of an event handler that can simulate an 'ENTER' key press prior to loss of focus of these elements?

Thank you in advance for your consideration.

The implementation of ComboBoxPopupControl (which in fact is the relevant base skin for all combo-like controls) changed somewhere between u40 and u60: the textField - including all internal wirings - was pulled up from the concrete skins (like fi ComboBoxListViewSkin) into the base.

Along with this mere technicality the handler for ENTER was changed:

  • was: forward all keyEvents that are received by the combo to the textField
  • is: handle and consume the ENTER in the base skin. This implementation manually commits the input text from the field to the combo's value.

A dirty (!because needs access to the internal package com.sun.whatever.skin) way out is a custom skin that listens to the focusProperty and calls the commit method, something like:

public class MyComboSkin<T> extends ComboBoxListViewSkin<T> {

    public MyComboSkin(ComboBox<T> comboBox) {
        super(comboBox);
        getSkinnable().focusedProperty().addListener((source, ov, nv) -> {
            if (!nv) {
                setTextFromTextFieldIntoComboBoxValue();
            }
        });
    }
}

The advantage of applying a custom skin is that it can be applied once per scene by adding a styleSheet:

// defining the skin in a css mycomboskin.css 
.combo-box {
    -fx-skin: "mypackage.MyComboSkin";
}

// apply to a scene
String commitCSS = getClass().getResource("mycomboskin.css").toExternalForm();
scene.getStylesheets().add(commitCSS);

// or to the application (using internal api again ;-)
StyleManager.getInstance().addUserAgentStylesheet(commitCSS) 

BTW: I think the new implementation in core is rather dirty - all keyBindings should be handled either in the XXBehaviour or alternatively left to the lower-level children (like the textField itself). The change in behaviour (verified against 8u45) might be considered a bug.


Update

An alternative trick is using a TextFormatter on the combo's editor and bidi-bind it's valueProperty to the combo's valueProperty, something like:

TextFormatter formatter = 
        new TextFormatter<>(comboBox.getConverter());
comboBox.getEditor().setTextFormatter(formatter);
comboBox.valueProperty().bindBidirectional(formatter.valueProperty());

This does work because the formatter guarantees to commit - aka: sync its own value to the textField's text - on focusLost (more details in a similar requirement for Spinner ) Note that a side-effect of this approach is that the text is committed on navigation inside the drop-down which might or not be acceptable depending on context. Also, it's more experimenting with TextFormatters than a dedicated workaround - needs the same per-instance manipalution as the workaround in the other solution by Scott .


The bug is fixed in jdk9 and backported 8u72, so any workaround hopefully is short-lived, choosing the once or other and going as dirty as necessary is likely a matter of taste :-)

Here's my workaround:

    combo.getEditor().focusedProperty().addListener((obs, old, isFocused) -> { 
        if (!isFocused) { 
            combo.setValue(combo.getConverter().fromString(combo.getEditor().getText()));
        } 
    }); 

As is pointed out in the official bug report , you might want to check to see if the value is already set to avoid any potential side-effects of calling it again.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM