2 votes

JavaFx: Les événements de touche du TextField ne sont pas déclenchés

Je suis en train de mettre en place un composant personnalisé où l'utilisateur peut taper dans un TextField et un TableView apparaît, puis l'utilisateur peut rechercher des éléments dans ce tableau.

J'ai un problème avec les événements KeyEvents du TextField standard comme Ctrl+A ou Home. Après l'apparition de la fenêtre avec le TableView, ces événements clés ne fonctionnent plus. J'ai vérifié si le TextField avait perdu le focus mais ce n'est pas le cas, et si je définis un EventFilter pour voir ce qui se passe, cela montre que ces événements sont déclenchés mais je ne vois aucun effet sur l'interface utilisateur. Même le setHideOnEscape de la popup ne fonctionne pas.

Voici un code simple pour le vérifier :

Le fichier fxml :

Le contrôleur :

public class Controller implements Initializable {

    @FXML
    private TextField textField;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        Popup popUp = new Popup();
        TableView table = new TableView<>();

        table.prefWidthProperty().bind(textField.widthProperty());
        popUp.getContent().add(table);
        popUp.setHideOnEscape(true);
        popUp.setAutoHide(true);

        // Pour voir si l'événement KeyEvent est déclenché
        textField.addEventFilter(KeyEvent.ANY, System.out::println);

        textField.setOnKeyTyped(event -> {
            if(!popUp.isShowing()){
                popUp.show(
                        textField.getScene().getWindow(),
                        textField.getScene().getWindow().getX()
                                + textField.localToScene(0, 0).getX()
                                + textField.getScene().getX(),
                        textField.getScene().getWindow().getY()
                                + textField.localToScene(0, 0).getY()
                                + textField.getScene().getY()
                                + textField.getHeight() - 1);
            }
        });
    }
}

Et le main :

public class Main extends Application {

    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("View.fxml"));
        AnchorPane pane = loader.load();
        primaryStage.setScene(new Scene(pane, 800, 600));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Voici la sortie de la console

KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_PRESSED, consumed = false, character =  , text = a, code = A]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_TYPED, consumed = false, character = a, text = , code = UNDEFINED]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_RELEASED, consumed = false, character =  , text = a, code = A]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_PRESSED, consumed = false, character =  , text = , code = CONTROL, controlDown, shortcutDown]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_TYPED, consumed = false, character = , text = , code = UNDEFINED, controlDown, shortcutDown]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_RELEASED, consumed = false, character =  , text = a, code = A, controlDown, shortcutDown]
KeyEvent [source = TextField[id=textField, styleClass=text-input text-field], target = TextField[id=textField, styleClass=text-input text-field], eventType = KEY_RELEASED, consumed = false, character =  , text = , code = CONTROL]

Une idée de pourquoi ces événements sont consommés même s'il est dit qu'ils ne le sont pas ?

1voto

kleopatra Points 31585

La raison pour laquelle le champ de texte ne réagit pas à certaines/toutes les "touches de fonction standard" est que leur type KEY_PRESSED ne lui parvient jamais - ils sont redirigés vers le tableau dans la popup et la plupart/tous sont consommés par le tableau.

Une première approximation naïve serait de définir la propriété focusTraversable du tableau sur false : cela empêche effectivement toutes les touches de lui être délivrées. Les exigences du monde réel pourraient être un peu moins simplistes, dans le sens où certaines d'entre elles devraient atteindre le tableau tandis que d'autres devraient remonter au champ de texte.

Cela peut être réalisé par un Dispatcher d'événements personnalisé (sur le tableau) qui examine tous les événements de touches et décide desquels être délivrés/non au Dispatcher d'origine du tableau. Un extrait de code où l'intercepteur est un prédicat utilisé pour la décision (à la fin, il y a un exemple complet de code fonctionnel pour plus de commodité) :

private BasicEventDispatcher original;
private Predicate interceptor;

@Override
public Event dispatchEvent(Event event, EventDispatchChain tail) {
    if (!interceptor.test(event)) {
        event = original.dispatchCapturingEvent(event);
        if (event.isConsumed()) {
            return null;
        }
    }
    event = tail.dispatchEvent(event);
    if (event != null && !interceptor.test(event)) {
        event = original.dispatchBubblingEvent(event);
        if (event.isConsumed()) {
            return null;
        }
    }
    return event;
}

Utilisation : si par exemple nous voulons que GAUCHE et DROITE remontent jusqu'au champ de texte, alors que tous les autres devraient être gérés normalement par le tableau

List toIntercept = List.of(KeyCode.LEFT, KeyCode.RIGHT);
Predicate interceptor = e -> {
    if (e instanceof KeyEvent) {
        return toIntercept.contains(((KeyEvent) e).getCode());
    }
    return false;
};
table.setEventDispatcher(new InterceptingEventDispatcher(
        (BasicEventDispatcher) table.getEventDispatcher(), interceptor));

Un exemple complet pour tester :

public class ViewPopupApplication extends Application {

    public static class InterceptingEventDispatcher implements EventDispatcher {
        private BasicEventDispatcher original;
        private Predicate interceptor;

        public InterceptingEventDispatcher(BasicEventDispatcher original, Predicate interceptor) {
            this.original = original;
            this.interceptor = interceptor;
        }

        @Override
        public Event dispatchEvent(Event event, EventDispatchChain tail) {
            if (!interceptor.test(event)) {
                event = original.dispatchCapturingEvent(event);
                if (event.isConsumed()) {
                    return null;
                }
            }
            event = tail.dispatchEvent(event);
            if (event != null && !interceptor.test(event)) {
                event = original.dispatchBubblingEvent(event);
                if (event.isConsumed()) {
                    return null;
                }
            }
            return event;
        }

    }

    private Parent createContent() {
        TableView table = new TableView<>(FXCollections.observableArrayList(Locale.getAvailableLocales()));
        // just to see that right/left are intercepted while up/down are handled
        table.getSelectionModel().setCellSelectionEnabled(true);

        TableColumn country = new TableColumn<>("Country");
        country.setCellValueFactory(new PropertyValueFactory<>("displayCountry"));
        TableColumn language = new TableColumn<>("Language");
        language.setCellValueFactory(new PropertyValueFactory<>("displayLanguage"));
        table.getColumns().addAll(country, language);
        // disables default focus traversal
        //  table.setFocusTraversable(false);

        // decide which keys to intercept
        List toIntercept = List.of(KeyCode.LEFT, KeyCode.RIGHT);
        Predicate interceptor = e -> {
            if (e instanceof KeyEvent) {
                return toIntercept.contains(((KeyEvent) e).getCode());
            }
            return false;
        };
        table.setEventDispatcher(new InterceptingEventDispatcher(
                (BasicEventDispatcher) table.getEventDispatcher(), interceptor));

        TextField textField = new TextField("something to show");
        textField.setPrefColumnCount(20);
        textField.setText("something to see");

        table.prefWidthProperty().bind(textField.widthProperty());
        Popup popUp = new Popup();
        popUp.getContent().add(table);

        textField.setOnKeyTyped(event -> {
            if(!popUp.isShowing()){
                popUp.show(
                        textField.getScene().getWindow(),
                        textField.getScene().getWindow().getX()
                                + textField.localToScene(0, 0).getX()
                                + textField.getScene().getX(),
                        textField.getScene().getWindow().getY()
                                + textField.localToScene(0, 0).getY()
                                + textField.getScene().getY()
                                + textField.getHeight() - 1);
            }
        });

        BorderPane content = new BorderPane(textField);
        return content;
    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent()));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X