【问题标题】:Manually typing in text in JavaFX Spinner is not updating the value (unless user presses ENTER)在 JavaFX Spinner 中手动输入文本不会更新值(除非用户按 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 微调控件的约定。

【问题讨论】:

    标签: java javafx spinner javafx-8


    【解决方案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()));
                }   
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2015-09-26
          • 1970-01-01
          • 2011-10-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-08
          相关资源
          最近更新 更多