Tommo
Tommo

Reputation: 987

Best practice since JavaFX setters/getters are final?

In the process of converting a Windows application from Swing to JavaFX. We had a lot of custom components that previously overrode the mutator methods of JTextField, for example. In JavaFX these methods are declared final. Should I just be creating wrapper methods that call the final methods and modify the values before and after? I just want to make sure I go about doing it the right way from the beginning.

Edit: I will also include the fact that some of this code is from much older versions of Java. So it's possible certain things are no longer necessary. This is an example of a setText method we had in a custom JFormattedTextField:

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

Upvotes: 1

Views: 1191

Answers (1)

jewelsea
jewelsea

Reputation: 159576

Using Listeners to be notified of changes in mutated state

Almost everything exposed in the JavaFX public API is a property, so one way to hook into mutate operations is to add ChangeListeners (or InvalidationListeners).

See the JavaFX property documentation for more information (the following snippet is copied from there):

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

For Java 8+, you could write:

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

If you are subclassing, you can setup listeners in the constructor of your subclass. These listeners can perform work similar to what you were doing in your Swing classes when you were overriding setter functions:

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

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

Answers to additional questions

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

Well you could and I've done it before. I am not sure that it is a best practice to do so (though I don't know of another way to handle it in general). I do recall the JavaFX property designer remarking on some forum that they didn't really design the property mechanism for updates within change listeners, so I guess it is not guaranteed.

I have run into issues where changing the location property of a WebView in a change listener caused threading issues, but I think that was an extremely unusual case due to the complex nature of WebView - I filed an accepted bug report on that particular case.

For other controls and properties you are probably fine using the "update in a listener" approach (though see the caveats in the answer to the next follow-up question).

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

No, I dont think it creates an infinite loop. I think there is something in the JavaFX listener implementation which stops it from looping indefinitely. I haven't checked the source code to see what that is and if my recollection is incorrect you would need to implement specific logic to prevent recursion which would be completely undesirable as it would complicate things a lot unnecessarily.

Due to suspected the short circuiting logic to prevent the infinite recursion if you set the value of the property within the change listener, I don't really know if the changed value would propagate to all of the listeners or items bound to that property (you would need to examine the implementation or write some tests to check behavior).

So those are a few reasons why I stated that, while it will probably work in most cases, setting values in the change listener is probably not a best practice.

How about altering the getter?

This use case seems to occur less often than listening on set values.

I suggest to create a new property and setup a bidirectional binding with a string converter. The use of a string convertor is optional, but it shows how you can listen on a transformed value.

Specific to formatted text fields

Your question is quite generic, but the problem you discuss in your post is quite specific to formatted text fields. The generic case is what I attempted to answer previously. For the specific case of formatted text fields there are specific solutions which may apply.

Richard recommends using a different API altogether, replaceText which is only available for 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's post is quite old now. For Java 8u40, the following feature will be included:

Another related open-jfx implementation item is:

That's not currently 9, but once implemented, might help provide some further concrete use cases for what you asking and a developer notes on the ticket "we believe that it is sufficient to provide example code for 8u40".

The third party JideFX library includes a FormattedTextField class.

Some background info on why things in the JavaFX API are final

Quote from Richard Bair, lead JavaFX developer:

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.

A suggestion

What you might want to do instead of overriding a getter for something like text input formatter is to have different APIs for the format mask versus the unformatted value text versus the formatted value text. I think this is probably what was done to add formatted JavaFX TextFields to Java 8u40.

Look at EasyBind

The third party EasyBind library may have some good recipes for dealing with the patterns you would like to implement.

Upvotes: 7

Related Questions