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

似乎Spinner控件在用户明确按Enter键之前不会更新手动键入的值。 因此,他们可以键入一个值(不按回车键)退出控件,并提交表单,并且微调器中显示的值不是微调器的值,它是旧值。

我的想法是为丢失的焦点事件添加一个监听器,但我看不到一种获取输入值的方法?

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

这是奇怪的行为,它似乎违反了GUI微调控件的惯例。

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

因此,我们必须手动将侦听器中的值提交给focusedProperty。 从好的方面来看,Spinner已经有代码这样做 – 它是私有的,但我们必须对它进行c&p

 /** * c&p from Spinner */ private  void commitEditorText(Spinner spinner) { if (!spinner.isEditable()) return; String text = spinner.getEditor().getText(); SpinnerValueFactory valueFactory = spinner.getValueFactory(); if (valueFactory != null) { StringConverter 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() 

我本来期望……好吧……提交价值,这没有效果。 实现(final!)以更新textFormatter的值(如果可用)。 即使您使用textFormatter进行validation ,也无法在Spinner中工作。 可能是一些内部听众失踪或者旋转器还没有更新到相对较新的api – 但是没有挖掘。


更新

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

当控件失去焦点或提交时,该值会更新(仅限TextField)

这确实有效,因为我们可以向格式化程序的valueProperty添加一个监听器,以便在提交值时得到通知:

 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 } ); 

提交的触发器:

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

回到微调器:我们可以使用formatter的值的这个commit-on-focusLost行为来强制提交spinnerFactory的值。 就像是

 // 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()); 

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

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

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

根据文档,这是控件的标准行为:

editable属性用于指定是否可以在Spinner编辑器中键入用户输入。 如果editable为true,则在用户键入并按Enter键后将收到用户输入。 此时输入传递给SpinnerValueFactory转换器StringConverter.fromString(String)方法。 然后将此调用(类型为T)的返回值发送到SpinnerValueFactory.setValue(Object)方法。 如果该值有效,则它将保留为值。 如果它无效,则值factory将需要相应地做出反应并退出此更改。

也许您可以使用键盘事件来监听并在控件上调用编辑提交。

这是Sergio解决方案的改进版本。

initialize方法将Sergio的代码附加到控制器中的所有Spinners。

 public void initialize(URL location, ResourceBundle resources) { for (Field field : getClass().getDeclaredFields()) { try { Object obj = field.get(this); if (obj != null && obj instanceof Spinner) ((Spinner) obj).focusedProperty().addListener((observable, oldValue, newValue) -> { if (!newValue) { ((Spinner) obj).increment(0); } }); } catch (IllegalAccessException e) { e.printStackTrace(); } } } 

使用监听器应该可行。 您可以通过微调器的编辑器访问键入的值:

 spinner.getEditor().getText(); 

我使用另一种方法 – 在键入时更新它。 这是我目前的实施:

 getEditor().textProperty().addListener { _, _, nv -> // let the user clear the field without complaining if(nv.isNotEmpty()) { Double newValue = getValue() try { newValue = getValueFactory().getConverter().fromString(nv) } catch (Exception e) { /* user typed an illegal character */ } getValueFactory().setValue(newValue) }