java 在 JavaFX Spinner 中手动输入文本不会更新值(除非用户按 ENTER)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32340476/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Manually typing in text in JavaFX Spinner is not updating the value (unless user presses ENTER)
提问by James Wierzba
It seems that the Spinner control does not update a manually typed-in value until the user explicitly presses enter. So, they could type in a value (not press enter) exit the control, and submit the form, and the value displayed in the spinner is NOT the value of the Spinner, it is the old value.
似乎 Spinner 控件在用户明确按下 Enter 之前不会更新手动输入的值。因此,他们可以输入一个值(而不是按 Enter 键)退出控件并提交表单,并且微调器中显示的值不是微调器的值,而是旧值。
My idea was to add a listener to the lost focus event, but I can't see a way to gain access to the typed-in value?
我的想法是为失去焦点事件添加一个侦听器,但我看不到访问输入值的方法?
spinner.focusedProperty().addListener((observable, oldValue, newValue) ->
{
//if focus lost
if(!newValue)
{
//somehow get the text the user typed in?
}
});
This is odd behavior, it seems to go against the convention of a GUI spinner control.
这是奇怪的行为,它似乎违反了 GUI 微调控件的约定。
回答by kleopatra
Unfortunately, Spinner doesn't behave as expected: in most OS, it should commit the edited value on focus lost. Even more unfortunate, it doesn't provide any configuration option to easily make it behave as expected.
不幸的是,Spinner 的行为不像预期的那样:在大多数操作系统中,它应该在失去焦点时提交编辑后的值。更不幸的是,它没有提供任何配置选项来轻松使其按预期运行。
So we have to manually commit the value in a listener to the focusedProperty. On the bright side, Spinner already has code doing so - it's private, though, we have to c&p it
所以我们必须手动将侦听器中的值提交给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);
});
Note that there's a method
注意有一个方法
textField.commitValue()
which I would have expected to ... well ... commit the value, which has no effect. It's (final!) implemented to update the value of the textFormatter if available. Doesn't work in the Spinner, even if you use a textFormatter for validation. Might be some internal listener missing or the spinner not yet updated to the relatively new api - didn't dig, though.
我本来期望......好吧......提交价值,这没有任何影响。它(最终!)实施以更新 textFormatter 的值(如果可用)。在 Spinner 中不起作用,即使您使用textFormatter 进行验证。可能是缺少一些内部侦听器,或者微调器尚未更新到相对较新的 api - 不过没有挖掘。
Update
更新
While playing around a bit more with TextFormatter I noticed that a formatter guaranteesto commiton focusLost:
在使用 TextFormatter 进行更多操作时,我注意到格式化程序保证提交focusLost:
The value is updated when the control loses its focus or it is commited (TextField only)
当控件失去焦点或提交时更新该值(仅限 TextField)
Which indeed works as documented such that we could add a listener to the formatter's valueProperty to get notified whenever the value is committed:
这确实如文档所示那样工作,以便我们可以向格式化程序的 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
} );
Triggers for a commit:
提交的触发器:
- user hits ENTER
- control looses focus
- field.setText is called programmatically (this is undocumented behaviour!)
- 用户点击 ENTER
- 控制失去焦点
- field.setText 以编程方式调用(这是未记录的行为!)
Coming back to the spinner: we can use this commit-on-focusLost behaviour of a formatter's value to force a commit on the spinnerFactory's value. Something like
回到微调器:我们可以使用格式化程序值的这种 commit-on-focusLost 行为来强制提交微调器工厂的值。就像是
// 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());
Note that editing (either typing or programmatically replacing/appending/pasting text) does nottrigger a commit - so this cannot be used if commit-on-text-change is needed.
需要注意的是编辑(无论是打字或以编程方式替换/追加/粘贴文本),并没有引发提交-所以,如果提交上文本变化需要此不能使用。
回答by Sergio
@kleopatra headed to a right direction, but the copy-paste solution feels awkward and the TextFormatter-based one did not work for me at all. So here's a shorter one, which forces Spinner to call it's private commitEditorText() as desired:
@kleopatra 朝着正确的方向前进,但复制粘贴解决方案感觉很尴尬,基于 TextFormatter 的解决方案对我来说根本不起作用。所以这是一个较短的,它迫使 Spinner 根据需要调用它的私有 commitEditorText():
spinner.focusedProperty().addListener((observable, oldValue, newValue) -> {
if (!newValue) {
spinner.increment(0); // won't change value, but will commit editor
}
});
回答by Robert
Here is an improved variant of Sergio's solution.
这是 Sergio 解决方案的改进变体。
The initialize method will attach Sergio's code to all Spinners in the controller.
initialize 方法会将 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();
}
}
}
回答by purring pigeon
This is standard behavior for the control according to the documentation:
根据文档,这是控件的标准行为:
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.
editable 属性用于指定用户输入是否能够输入到 Spinner 编辑器中。如果 editable 为 true,则在用户键入并按下 Enter 键后将接收用户输入。此时,输入被传递给 SpinnerValueFactory 转换器 StringConverter.fromString(String) 方法。此调用的返回值(类型 T)然后被发送到 SpinnerValueFactory.setValue(Object) 方法。如果该值有效,它将保留为该值。如果它无效,值工厂将需要做出相应的反应并撤消此更改。
Perhaps you could use a keyboard event to listen to and call the edit commit on the control as you go.
也许您可以使用键盘事件来侦听和调用控件上的编辑提交。
回答by Amber
Using a listener should work. You can get access to the typed in value through the spinner's editor:
使用侦听器应该可以工作。您可以通过微调器的编辑器访问键入的值:
spinner.getEditor().getText();
回答by Xerus
I use an alternate approach - update it live while typing. This is my current implementation:
我使用另一种方法 - 打字时实时更新它。这是我目前的实现:
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)
}