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

Posted

技术标签:

【中文标题】在 JavaFX Spinner 中手动输入文本不会更新值(除非用户按 ENTER)【英文标题】:Manually typing in text in JavaFX Spinner is not updating the value (unless user presses ENTER) 【发布时间】:2015-11-27 05:07:15 【问题描述】:

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

我的想法是为失去焦点事件添加一个侦听器,但我看不到获得对输入值的访问权限的方法?

spinner.focusedProperty().addListener((observable, oldValue, newValue) -> 

    //if focus lost
    if(!newValue)
    
        //somehow get the text the user typed in?
    
);

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

【问题讨论】:

【参考方案1】:

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

所以我们必须手动将侦听器中的值提交给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 的值(如果可用)。即使您使用textFormatter for validation,在微调器中也不起作用。可能是缺少一些内部侦听器,或者微调器尚未更新到相对较新的 api - 不过没有挖掘。


更新

在使用 TextFormatter 进行更多操作时,我注意到在 focusLost 上的格式化程序 guarantees to commit:

当控件失去焦点或提交时更新值(仅限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 以编程方式调用(这是未记录的行为!)

回到微调器:我们可以使用格式化程序值的这种 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());

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

【讨论】:

对于那些来寻找这个答案的人来说,为什么他们的微调器在按下回车后没有更新:你可能正在使用 ChangeListener - 它只有在值发生真正变化时才会激活, JavaFX 有时似乎会出错。尝试使用 InvalidationListener。 @kleopatra 我希望该网站通知您有关此评论,我对the answer below from Sergio 有疑问。你对此有什么想法吗?这似乎是一个简短而甜蜜的解决方案,感觉好得令人难以置信。它只比你的新一年,如果它那么好,我希望它现在会超过你的答案分数。塞尔吉奥的回答是否有需要注意的问题?还是人们一找到您的解决方案就停止滚动?当我试图在该答案下标记您时,我认为它不起作用。 @pateksan 很长一段时间没有关注这个问题(忘记了底层错误是否同时得到修复)——另一个答案是基于未指定的实现细节的破解。这种行为有点出乎意料,IMO - 我们也可以在编辑时拒绝 inc/dec。但是,如果没有提供某些东西,我们必须去做任何工作:) @kleopatra,谢谢。我还有另一个问题(对不起,如果我把你拖入一个老问题):当你说我们在编辑时也可以拒绝 inc/dec 时,我有点困惑?拒绝 inc/dec 似乎与 OPs 问题无关。这是塞尔吉奥代码的副作用吗?还是实现 OP 最初目标的另一种方法?另外,我还是新手,但如果你把它放在塞尔吉奥的回答而不是你的回答下,你的评论(上面的评论,就在一个小时前)可能对每个人都有用 - 如果建议这个是不礼貌的,我很抱歉代表多 2^8 的人。 @pateksan 简短(也是最后一个)回答:是的 - 对不起,这太离题了 - 要了解所有关于引用的知识,请阅读与弱/强引用相关的 Java 基础教程并将你学到的知识应用到这个上下文中(通过思考这里的参考路径,编写测试,在卡住时提出问题.. :) 玩得开心!【参考方案2】:

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

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

【讨论】:

谢谢!像魅力一样工作。 不错的答案。工作过 @kleopatra 和其他任何人:如果这是一个如此简短而甜蜜的解决方案,为什么它仍然没有超过 kleopatra 答案的分数?这两个答案现在几乎都在 5 岁左右。这个答案是否存在用户需要注意的问题?人们只是向下滚动得不够远吗?【参考方案3】:

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

editable 属性用于指定用户输入是否能够 在 Spinner 编辑器中输入。如果 editable 为真,用户输入将 一旦用户键入并按下 Enter 键,就会收到。在这 点输入传递给 SpinnerValueFactory 转换器 StringConverter.fromString(String) 方法。返回值来自 这个调用(类型 T)然后被发送到 SpinnerValueFactory.setValue(Object) 方法。如果该值有效,则 将保留为值。如果无效,值工厂将 需要做出相应的反应并取消此更改。

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

【讨论】:

你确定了文档 :-) 但是:与往常一样,使用低级事件 (fi keyEvent) 作为触发器来验证/提交文本是一个坏主意,因为还有其他输入方式(如 fi粘贴) 你可以试试这个.... spinner.getEditor().textProperty().addListener((observable, oldValue, newValue) -> commitEditorText(); ); 是的,如果您想在输入/更改任何文本时提交值【参考方案4】:

这是 Sergio 解决方案的改进变体。

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

【讨论】:

【参考方案5】:

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

spinner.getEditor().getText();

【讨论】:

也许你忘记了解释如何做到这一点的部分? 原始问题显示了如何添加侦听器,OP 不确定的部分是“一种访问输入值的方法”,如我的回答所示。【参考方案6】:

我使用另一种方法 - 在输入时实时更新。这是我目前的实现:

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)
    

【讨论】:

【参考方案7】:

我使用了这种方法

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

【讨论】:

以上是关于在 JavaFX Spinner 中手动输入文本不会更新值(除非用户按 ENTER)的主要内容,如果未能解决你的问题,请参考以下文章

Spinner 不显示数组和文本

如何格式化文本字段javafx

Spinner 旋转器

单击 JavaFX 突出显示 TextField 中的所有文本

View(视图)——AutoCompleteTextView Spinner和消息提示

如何仅更改 javafx TextField 中最后一个字符的颜色