Search code examples
javajavafxevent-handlingconventions

Is this cast okay?


I have an EventHandler that I set as an event filter on TextFields. When I write the class, I get the source TextField by calling getSource() on the event and casting it to a TextField.

The code for the EventHandler:

public class NumberFilter implements EventHandler<KeyEvent> {

    public final int maxLength;
    public NumberFilter(int maxLength) {
        this.maxLength = maxLength;
    }

    @Override
    public void handle(KeyEvent event) {
        TextField textField = (TextField) event.getSource(); //<-- is this cast okay?

        //consume event if there is too much text or the input isn't a number.
        if (textField.getText().length() >= maxLength || !event.getCharacter().matches("[0-9]")) {
            event.consume();
        }
    }
}

Is this cast okay by standard java conventions? How can I write the class so that it can't be used anywhere except as an event filter for a TextField?


Solution

  • Andy Turner's answer provides a robust general approach to allowing event handlers to be added to only one type of Node. However, for the specific case of vetoing changes to the text in a TextField (or other text input control), the approach of using key event handlers is not a good one for the following reasons:

    1. The user can bring up a context menu with the mouse and paste text in. This doesn't involve any key presses at all, so your handler won't be invoked.
    2. You have no control over which type of key events the text field uses internally. Are you registering this filter with KEY_PRESSED, KEY_RELEASED, or KEY_TYPED events? Are you sure the events used internally by the text field will remain the same from one JavaFX release to the next?
    3. You will likely inadvertently veto keyboard shortcuts such as Ctrl-C (for copy) or Ctrl-V (for paste), and similar. (If you don't veto shortcuts for "paste", you allow another loophole for the user to paste invalid text...). Again, it's possible a future release of JavaFX may introduce additional shortcuts, which it's virtually impossible to proof your functionality against.

    For completeness, the preferred approach for this particular use case is as follows:

    Use a TextFormatter, which is the supported mechanism for vetoing or modifying text entry to a text input control (as well as providing mechanisms to format or parse text in the control). You can make this reusable by implementing the filter in a standalone class:

    public class NumberFilter implements UnaryOperator<TextFormatter.Change> {
    
        private final Pattern pattern ;
    
        public NumberFilter(int maxLength) {
            pattern = Pattern.compile("[0-9]{0,"+maxLength+"}");
        }
    
        @Override
        public TextFormatter.Change apply(TextFormatter.Change c) {
            String newText = c.getControlNewText() ;
            if (pattern.matcher(newText).matches()) {
                return  c ;
            } else {
                return null ;
            }
        }
    }
    

    And now you can do

    TextField textField = new TextField();
    textField.setTextFormatter(new TextFormatter<String>(new NumberFilter(5)));