在 JavaFX Spinner 中手动键入文本不会更新该值(除非用户按 Enter)

2022-09-01 11:25:10

在用户显式按 Enter 键之前,微调控件似乎不会更新手动键入的值。因此,他们可以键入一个值(不按 Enter)退出控件,并提交窗体,并且微调器中显示的值不是微调器的值,而是旧值。

我的想法是为失焦事件添加一个侦听器,但我看不到访问键入值的方法?

spinner.focusedProperty().addListener((observable, oldValue, newValue) -> 
{
    //if focus lost
    if(!newValue)
    {
        //somehow get the text the user typed in?
    }
});

这是奇怪的行为,它似乎违背了GUI微调控件的约定。


答案 1

不幸的是,Spinner的行为不符合预期:在大多数操作系统中,它应该在焦点丢失时提交编辑的值。更不幸的是,它没有提供任何配置选项来轻松使其按预期运行。

因此,我们必须手动将侦听器中的值提交到聚焦属性。从好的方面来说,Spinner已经有代码这样做了 - 它是私有的,但是,我们必须c&p它

/**
 * c&p from Spinner
 */
private <T> void commitEditorText(Spinner<T> spinner) {
    if (!spinner.isEditable()) return;
    String text = spinner.getEditor().getText();
    SpinnerValueFactory<T> valueFactory = spinner.getValueFactory();
    if (valueFactory != null) {
        StringConverter<T> converter = valueFactory.getConverter();
        if (converter != null) {
            T value = converter.fromString(text);
            valueFactory.setValue(value);
        }
    }
}

// useage in client code
spinner.focusedProperty().addListener((s, ov, nv) -> {
    if (nv) return;
    //intuitive method on textField, has no effect, though
    //spinner.getEditor().commitValue(); 
    commitEditorText(spinner);
});

请注意,有一种方法

textField.commitValue()

我本来以为...井。。。提交值,该值不起作用。它是(最终的!)实现,以更新文本格式化程序的值(如果可用)。在微调器中不起作用,即使您使用文本格式化程序进行验证也是如此。可能是缺少一些内部侦听器,或者微调器尚未更新到相对较新的api - 但是没有挖掘。


更新

在使用TextFormatter进行更多操作时,我注意到格式化程序保证提交focusLost

当控件失去焦点或提交时,将更新该值(仅限文本字段)

这确实按照文档记录工作,因此我们可以向格式化程序的值Property添加一个侦听器,以便在提交值时收到通知:

TextField field = new TextField();
TextFormatter fieldFormatter = new TextFormatter(
      TextFormatter.IDENTITY_STRING_CONVERTER, "initial");
field.setTextFormatter(fieldFormatter);
fieldFormatter.valueProperty().addListener((s, ov, nv) -> {
    // do stuff that needs to be done on commit
} );

提交的触发器:

  • 用户点击回车
  • 控制失去焦点
  • field.setText 以编程方式调用(这是未记录的行为!

回到微调器:我们可以使用格式化程序值的这种焦点提交丢失行为来强制对微调器Factory的值进行提交。类似的东西

// normal setup of spinner
SpinnerValueFactory factory = new IntegerSpinnerValueFactory(0, 10000, 0);
spinner.setValueFactory(factory);
spinner.setEditable(true);
// hook in a formatter with the same properties as the factory
TextFormatter formatter = new TextFormatter(factory.getConverter(), factory.getValue());
spinner.getEditor().setTextFormatter(formatter);
// bidi-bind the values
factory.valueProperty().bindBidirectional(formatter.valueProperty());

请注意,编辑(键入或以编程方式替换/追加/粘贴文本)不会触发提交 - 因此,如果需要对文本进行提交更改,则不能使用这种操作。


答案 2

@kleopatra朝着正确的方向前进,但是复制粘贴解决方案感觉很尴尬,基于TextFormatter的解决方案根本不适合我。所以这里有一个较短的,它迫使Spinner根据需要将其称为私有 commitEditorText():

spinner.focusedProperty().addListener((observable, oldValue, newValue) -> {
  if (!newValue) {
    spinner.increment(0); // won't change value, but will commit editor
  }
});

推荐