最佳实践,因为 JavaFX setters/getters 是最终的?

Best practice since JavaFX setters/getters are final?

正在将 Windows 应用程序从 Swing 转换为 JavaFX。例如,我们有很多以前覆盖 JTextField 的更改器方法的自定义组件。在 JavaFX 中,这些方法被声明为最终方法。我是否应该只创建包装器方法来调用最终方法并修改前后的值?我只是想确保我从一开始就以正确的方式去做。

编辑:我还将包括一些代码来自 Java 的更旧版本的事实。所以某些东西可能不再需要了。这是我们在自定义 JFormattedTextField 中使用的 setText 方法的示例:

public void setText(String text) {
    String newString = "";
    if (mask == null) {
        newString = text;
        unformattedCurrent = newString;
    } else {
        newString = applyMask(text);
    }
    super.setText(newString);
    if (text.trim().length() == 0) {
        positionCaret(0);
        selectRange(0, 0);
    } else {
        int length = getFormattedText().length();
        if (isFocused() && length == getCaretPosition()) {
            super.selectAll();
        }
    }
}

使用侦听器接收变异状态变化的通知

JavaFX public API 中几乎所有内容都是 属性,因此挂钩变异操作的一种方法是添加 ChangeListeners (or InvalidationListeners ).

有关详细信息,请参阅 JavaFX property documentation(以下代码段是从那里复制的):

import javafx.beans.value.ObservableValue;
import javafx.beans.value.ChangeListener;

public class Main {     
    public static void main(String[] args) {    
        Bill electricBill = new Bill();

        electricBill.amountDueProperty().addListener(new ChangeListener() {
            @Override public void changed(
                 ObservableValue o, 
                 Object oldVal, 
                 Object newVal
            ) {
               System.out.println("Electric bill has changed!");
            }
        });

      electricBill.setAmountDue(100.00);         
    }
}

对于Java 8+,你可以这样写:

Label billAmountDue = new Label();
billAmountDue.textProperty().addListener((observable, oldVal, newVal) -> 
    System.out.println("Electric bill has changed!")
);

如果您正在子类化,则可以在子类的构造函数中设置侦听器。当您覆盖 setter 函数时,这些侦听器可以执行类似于您在 Swing 类 中所做的工作:

public class AmountLabel extends Label {
    public AmountLabel(String text) {
        super(text);

        textProperty().addListener(observable -> 
            System.out.println("Amount invalidated.")
        );
    }
}

其他问题的答案

So within the change listener I would then set the appropriate value after any mask is set?

你可以,我以前也做过。我不确定这样做是否是最佳做法(尽管我不知道一般的另一种处理方法)。我确实记得 JavaFX 属性 设计师在某个论坛上评论说他们并没有真正设计 属性 变更侦听器中的更新机制,所以我想这不能保证。

我遇到了 运行 更改 WebView 在更改侦听器中的位置 属性 导致线程问题的问题,但由于 WebView 的复杂性,我认为这是一个非常不寻常的案例- 我就该特定案例提交了一份已接受的错误报告。

对于其他控件和属性,您可能可以使用 "update in a listener" 方法(尽管请参阅下一个后续问题的答案中的注意事项)。

Wouldn't setting the value within the listener create an infinite loop since it would keep going into the ChangeListener upon updating the value.

不,我认为它不会造成无限循环。我认为 JavaFX 侦听器实现中有一些东西可以阻止它无限期地循环。我没有检查源代码以查看那是什么,如果我的回忆不正确,您将需要实施特定的逻辑来防止递归,这将是完全不可取的,因为它会使事情变得不必要地复杂化。

如果您在更改侦听器中设置 属性 的值,我怀疑短路逻辑会阻止无限递归,我真的不知道更改后的值是否会传播到所有绑定到 属性 的侦听器或项目(您需要检查实现或编写一些测试来检查行为)。

所以这就是我说的几个原因,虽然它可能在大多数情况下都有效,但在更改侦听器中设置值可能不是最佳做法。

How about altering the getter?

这个用例的出现频率似乎低于监听设定值。

我建议创建一个新的 属性 并设置一个 bidirectional binding with a string converter。字符串转换器的使用是可选的,但它展示了如何监听转换后的值。

特定于格式化文本字段

您的问题很笼统,但是您在 post 中讨论的问题是针对格式化文本字段的。一般情况是我之前试图回答的。对于格式化文本字段的特定情况,有可能适用的特定解决方案。

Richard recommends using a different API altogether, replaceText 仅适用于 TextInput:

field = new TextField() {
    @Override public void replaceText(int start, int end, String text) {
        // If the replaced text would end up being invalid, then simply
        // ignore this call!
        if (!text.matches("[a-z]")) {
            super.replaceText(start, end, text);
        }
    }

    @Override public void replaceSelection(String text) {
        if (!text.matches("[a-z]")) {
            super.replaceSelection(text);
        }
    }
};

Richard 的 post 现在已经很老了。对于 Java 8u40,将包括以下功能:

另一个相关的open-jfx实现项目是:

目前不是 9,但一旦实施,可能有助于为您的要求提供一些更具体的用例,并且开发人员会在票证上注明 "we believe that it is sufficient to provide example code for 8u40"。

第三方JideFX library includes a FormattedTextField class.

关于为什么 JavaFX API 中的东西是最终的一些背景信息

Quote from Richard Bair,领导 JavaFX 开发人员:

Security for one. You can do evil things to non final classes! That is just a general rule (which is why generally we want to make everything final we can). In this particular case it may not matter.

一个建议

您可能想要做的不是为文本输入格式化程序覆盖 getter,而是为格式掩码、未格式化值文本和格式化值文本设置不同的 API .我认为这可能是将格式化的 JavaFX TextFields 添加到 Java 8u40.

所做的

看EasyBind

第三方 EasyBind 库可能有一些很好的方法来处理您想要实现的模式。