在 JavaFX Spinner 中手动输入文本不会更新值(除非用户按下 ENTER)
Manually typing in text in JavaFX Spinner is not updating the value (unless user presses ENTER)
似乎 Spinner 控件在用户明确按下 enter 之前不会更新手动输入的值。因此,他们可以输入一个值(而不是按回车键)退出控件并提交表单,并且微调器中显示的值不是微调器的值,而是旧值。
我的想法是为失去焦点事件添加一个侦听器,但我看不到获取输入值的方法?
spinner.focusedProperty().addListener((observable, oldValue, newValue) ->
{
//if focus lost
if(!newValue)
{
//somehow get the text the user typed in?
}
});
这是一个奇怪的行为,它似乎违背了 GUI 微调器控件的惯例。
根据文档,这是控件的标准行为:
The editable property is used to specify whether user input is able to
be typed into the Spinner editor. If editable is true, user input will
be received once the user types and presses the Enter key. At this
point the input is passed to the SpinnerValueFactory converter
StringConverter.fromString(String) method. The returned value from
this call (of type T) is then sent to the
SpinnerValueFactory.setValue(Object) method. If the value is valid, it
will remain as the value. If it is invalid, the value factory will
need to react accordingly and back out this change.
也许您可以使用键盘事件来监听和调用控件上的编辑提交。
使用侦听器应该可以。您可以通过微调器的编辑器访问输入的值:
spinner.getEditor().getText();
不幸的是,Spinner 的行为不如预期:在大多数 OS 中,它应该在失去焦点时提交编辑的值。更不幸的是,它没有提供任何配置选项来轻松使其按预期运行。
所以我们必须手动将侦听器中的值提交给 focusedProperty。从好的方面来说,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()
我本以为……好吧……提交值,但没有任何效果。它(最终!)已实施以更新 textFormatter 的值(如果可用)。在 Spinner 中不起作用,即使您使用 textFormatter for validation。可能缺少某些内部侦听器,或者微调器尚未更新为相对较新的 api - 虽然没有挖掘。
更新
在进一步使用 TextFormatter 时,我注意到 focusLost:
上的格式化程序 guarantees to commit
The value is updated when the control loses its focus or it is commited (TextField only)
这确实如文档所述那样工作,因此我们可以向格式化程序的 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 以编程方式调用(这是未记录的行为!)
回到微调器:我们可以使用格式化程序值的这种 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());
请注意,编辑(键入或以编程方式 replacing/appending/pasting 文本)不会 触发提交 - 因此如果 commit-on-text-change 是需要。
@kleopatra 朝着正确的方向前进,但复制粘贴解决方案感觉很尴尬,而基于 TextFormatter 的解决方案对我来说根本不起作用。所以这是一个较短的,它强制 Spinner 根据需要调用它的私有 commitEditorText() :
spinner.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
spinner.increment(0); // won't change value, but will commit editor
}
});
这是 Sergio 解决方案的改进变体。
初始化方法会将 Sergio 的代码附加到控制器中的所有 Spinner。
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();
}
}
}
我使用另一种方法 - 在键入时实时更新它。这是我当前的实现:
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)
}
我使用了这种方法
public class SpinnerFocusListener<T> implements ChangeListener<Boolean> {
private Spinner<T> spinner;
public SpinnerFocusListener(Spinner<T> spinner) {
super();
this.spinner = spinner;
this.spinner.getEditor().focusedProperty().addListener(this);
}
@Override
public void changed(ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue) {
StringConverter<T>converter=spinner.getValueFactory().getConverter();
TextField editor=spinner.getEditor();
String text=editor.getText();
try {
T value=converter.fromString(text);
spinner.getValueFactory().setValue(value);
}catch(Throwable ex) {
editor.setText(converter.toString(spinner.getValue()));
}
}
}
似乎 Spinner 控件在用户明确按下 enter 之前不会更新手动输入的值。因此,他们可以输入一个值(而不是按回车键)退出控件并提交表单,并且微调器中显示的值不是微调器的值,而是旧值。
我的想法是为失去焦点事件添加一个侦听器,但我看不到获取输入值的方法?
spinner.focusedProperty().addListener((observable, oldValue, newValue) ->
{
//if focus lost
if(!newValue)
{
//somehow get the text the user typed in?
}
});
这是一个奇怪的行为,它似乎违背了 GUI 微调器控件的惯例。
根据文档,这是控件的标准行为:
The editable property is used to specify whether user input is able to be typed into the Spinner editor. If editable is true, user input will be received once the user types and presses the Enter key. At this point the input is passed to the SpinnerValueFactory converter StringConverter.fromString(String) method. The returned value from this call (of type T) is then sent to the SpinnerValueFactory.setValue(Object) method. If the value is valid, it will remain as the value. If it is invalid, the value factory will need to react accordingly and back out this change.
也许您可以使用键盘事件来监听和调用控件上的编辑提交。
使用侦听器应该可以。您可以通过微调器的编辑器访问输入的值:
spinner.getEditor().getText();
不幸的是,Spinner 的行为不如预期:在大多数 OS 中,它应该在失去焦点时提交编辑的值。更不幸的是,它没有提供任何配置选项来轻松使其按预期运行。
所以我们必须手动将侦听器中的值提交给 focusedProperty。从好的方面来说,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()
我本以为……好吧……提交值,但没有任何效果。它(最终!)已实施以更新 textFormatter 的值(如果可用)。在 Spinner 中不起作用,即使您使用 textFormatter for validation。可能缺少某些内部侦听器,或者微调器尚未更新为相对较新的 api - 虽然没有挖掘。
更新
在进一步使用 TextFormatter 时,我注意到 focusLost:
上的格式化程序 guarantees to commitThe value is updated when the control loses its focus or it is commited (TextField only)
这确实如文档所述那样工作,因此我们可以向格式化程序的 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 以编程方式调用(这是未记录的行为!)
回到微调器:我们可以使用格式化程序值的这种 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());
请注意,编辑(键入或以编程方式 replacing/appending/pasting 文本)不会 触发提交 - 因此如果 commit-on-text-change 是需要。
@kleopatra 朝着正确的方向前进,但复制粘贴解决方案感觉很尴尬,而基于 TextFormatter 的解决方案对我来说根本不起作用。所以这是一个较短的,它强制 Spinner 根据需要调用它的私有 commitEditorText() :
spinner.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
spinner.increment(0); // won't change value, but will commit editor
}
});
这是 Sergio 解决方案的改进变体。
初始化方法会将 Sergio 的代码附加到控制器中的所有 Spinner。
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();
}
}
}
我使用另一种方法 - 在键入时实时更新它。这是我当前的实现:
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)
}
我使用了这种方法
public class SpinnerFocusListener<T> implements ChangeListener<Boolean> {
private Spinner<T> spinner;
public SpinnerFocusListener(Spinner<T> spinner) {
super();
this.spinner = spinner;
this.spinner.getEditor().focusedProperty().addListener(this);
}
@Override
public void changed(ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue) {
StringConverter<T>converter=spinner.getValueFactory().getConverter();
TextField editor=spinner.getEditor();
String text=editor.getText();
try {
T value=converter.fromString(text);
spinner.getValueFactory().setValue(value);
}catch(Throwable ex) {
editor.setText(converter.toString(spinner.getValue()));
}
}
}