Gracias por la ayuda en mi última publicación (que también es mi primera publicación). Soy nuevo en stackoverflow. Ojalá me hubiera unido al grupo antes. La gente es extremadamente amable y servicial aquí.

De todos modos, he estado trabajando para comprender mejor el evento javafx. A algunos de ustedes les puede parecer otra pregunta simple o "estúpida". ¿Por qué, de forma predeterminada, el controlador de eventos del botón del mouse parece consumir un evento?

De los documentos de Oracle, en la parte inferior de la página, establece que "Tenga en cuenta que los controladores predeterminados para los controles de la IU de JavaFX suelen consumir la mayoría de los eventos de entrada". ¿Es un controlador predeterminado adjunto al botón que no conozco? ¿Por qué tengo que EXPLÍCITAMENTE activar el evento en el nodo de destino para que aumente la cadena de distribución de eventos?

Nuevamente, ¡cualquier respuesta será muy apreciada! :)

public class MouseEventTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Hello World");

        Group root = new Group();

        Scene scene = new Scene(root, 300, 250);

        Button btn = new Button();
        btn.setText("Hello World");
        btn.setPrefSize(100, 100);


        BorderPane layout = new BorderPane(btn);
        layout.setPrefSize(300, 250);

        root.getChildren().add(layout);

        //This is the event dispatch chain
        //primaryStage -> scene -> root -> layout -> btn  (capturing phrase)
        //btn -> layout -> root -> scene -> primaryStage  (bubbling phrase)
        btn.setOnMousePressed(e -> { 
            System.out.println("btn mouse pressed...");
            //Why do I need to fire the mouse pressed event 
            //in order for the event to bubble up the chain?
            //It seems like by default that the button setOnMousePressed event hanlder
            //has consumed the event. Am I right?
            //layout.fireEvent(e); 
        });

        layout.setOnMousePressed(e -> { System.out.println("layout mouse pressed...");});
        root.setOnMousePressed(e -> { System.out.println("root mouse pressed...");});
        scene.setOnMousePressed(e -> { System.out.println("scene mouse pressed...");}); 

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

}
3
Tania 14 dic. 2016 a las 01:57

2 respuestas

La mejor respuesta

La implementación predeterminada de la máscara del botón invoca consumeMouseEvents(true). Si no desea este comportamiento, anule la máscara predeterminada y establezca el valor en falso.

btn.setSkin(new ButtonSkin(btn) {
    {
        this.consumeMouseEvents(false);
    }
});

Entonces el resultado de hacer clic en el botón para su aplicación de muestra será:

btn mouse pressed...
layout mouse pressed...
root mouse pressed...
scene mouse pressed...

Por qué funciona así, no podría decirlo. Supongo que si la máscara consume los eventos, esto ayuda a evitar la propagación no deseada de los eventos del mouse desde los controles, como cuando están en capas o apilados uno encima del otro. Que es probablemente el comportamiento que desea casi todo el tiempo y, por lo tanto, es un valor predeterminado razonable.

5
jewelsea 13 dic. 2016 a las 23:56

"Eventos de entrada" en la documentación que vinculó se refiere a subclases de eventos en paquete javafx.scene.input. Estos son eventos de "bajo nivel", como MouseEvent y KeyEvent. Por lo general, para un control, no está interesado en eventos como estos, sino en eventos "semánticos" de nivel superior, como ActionEvent.

Usando el botón como ejemplo, normalmente escribe código que se invoca cuando el usuario intenta realizar una "acción" con el botón. En realidad, esto podría suceder si el usuario hace clic en el botón con un mouse, o presiona la barra espaciadora cuando el botón tiene el foco del teclado, o presiona enter si el botón es el botón predeterminado, o presiona alguna pulsación de tecla que coincida con un mnemónico que está asociado con el botón . En todos estos ejemplos, el usuario tiene el mismo significado con la acción "física": esta es la "acción" asociada con el botón.

En consecuencia, para facilitarle la codificación, el botón encapsula todos estos comportamientos diferentes y los vuelve a empaquetar como una "acción". Para ello, registra a los oyentes de los eventos de bajo nivel (pulsaciones del ratón y pulsaciones de teclas, etc.). Si ocurren, entonces el botón mismo los maneja y dispara un evento de acción. Dado que ahora se considera que el evento de bajo nivel se gestiona, se consume, lo que evita que aumente la jerarquía del gráfico de escena.

Por lo tanto, normalmente debe buscar eventos de "alto nivel" o "semánticos" en un control como un botón:

btn.setOnAction(e -> System.out.println("Action performed on button"));

Si el usuario hace clic en un botón, (aparentemente) se considera que es una "acción" en ese botón, y no un clic en cualquier contenedor que contenga el botón. No puedo justificar completamente por qué se tomó esta decisión de diseño, pero por lo general, en mi humilde opinión, el evento de clic del mouse que no se dispara en el padre del botón tiene sentido.

Si realmente necesita escuchar los clics del mouse en el contenedor, incluso si realmente ocurren en un control que contiene el contenedor, puede usar un filtro de eventos para manejarlos antes de que lleguen al control:

layout.addEventFilter(MouseEvent.MOUSE_PRESSED, 
    e -> { System.out.println("layout mouse pressed...");});
5
James_D 13 dic. 2016 a las 23:58