Saltar a contenido

Eventos en JavaFX

Cada vez que un usuario interactúa con la aplicación (nodos), se dice que ha ocurrido un evento. Existe un UIThread que está escuchando cuando lanzamos la aplicación. Cuando el usuario hace algo el UIThread que está escuchando, mira a ver si se lanza un evento, y si es así, envía el evento al controlador de eventos (event handler). El Event Handler se ejecuta en el subproceso del hilo UIThread. Mientras se ejecuta un evento del UIThread el usuario no puede interactuar con la aplicación.

Por ejemplo, hacer clic en un botón, mover el ratón, ingresar un carácter a través del teclado, seleccionar un elemento de la lista, desplazarse por la página, etc. son ejemplos de actividades que provocan que suceda un evento.

Un evento notifica que ha ocurrido algo importante. Los eventos suelen ser la parte "primitiva" de un sistema de eventos (también conocido como bus de eventos). Generalmente, un sistema de eventos tiene las siguientes 3 responsabilidades:

  1. Trigger an event (disparar un evento)
  2. Notify listeners (notificar a los oyentes partes interesadas) sobre el evento
  3. Handle the event (manejar o procesar el evento).

El mecanismo de notificación de eventos lo realiza la plataforma JavaFX automáticamente. Por lo tanto, solo consideraremos cómo disparar eventos, escuchar eventos y cómo manejarlos.

Tipos de eventos.

  • Eventos en primer plano (foreground events): requieren la interacción directa de un usuario. Se generan como consecuencia de la interacción de una persona con los componentes gráficos en una interfaz gráfica de usuario. Por ejemplo, hacer clic en un botón, mover el ratón, ingresar un carácter a través del teclado, seleccionar un elemento de la lista, desplazarse por la página, etc.
  • Eventos de fondo (background events): no requieren la interacción del usuario final. Las interrupciones del sistema operativo, fallas de hardware o software, vencimiento del temporizador, finalización de la operación son ejemplos de eventos en segundo plano.

Algunos eventos en JavaFX.

La clase denominada Event del paquete javafx.event es la clase base para un evento. Una instancia de cualquiera de sus subclases es un evento. JavaFX proporciona una amplia variedad de eventos. Algunos de ellos son:

  • Mouse event: evento de entrada que ocurre cuando se hace clic con el ratón. Está representado por la clase denominada MouseEvent. Incluye acciones como hacer clic con el ratón, presionarlo, soltarlo, moverlo, objetivo ingresado con el ratón, objetivo salido del ratón, etc.
  • Key event: evento de entrada que indica que se produjo una pulsación de tecla en un nodo. Está representado por la clase denominada KeyEvent. Este evento incluye acciones como pulsación de tecla, liberación de tecla y escritura de tecla.
  • Drag event: evento de entrada que ocurre cuando se arrastra el ratón. Está representado por la clase llamada DragEvent. Incluye acciones como arrastrar para insertar, arrastrar para soltar, etc.
  • Window event: evento relacionado con las acciones de mostrar/ocultar ventanas. Está representado por la clase llamada WindowEvent.

Manejo de eventos.

El Manejo de Eventos es el mecanismo que controla el evento y decide qué debe suceder, si ocurre un evento. Este mecanismo tiene el código conocido como controlador de eventos que se ejecuta cuando ocurre un evento. JavaFX proporciona controladores y filtros para manejar eventos. En JavaFX cada evento tiene:

  • Destino (Target): el nodo en el que ocurrió un evento. Un objetivo puede ser una ventana, una escena y un nodo.
  • Fuente (Source): la fuente a partir de la cual se genera el evento será la fuente del evento. En el escenario anterior, el ratón es la fuente del evento.
  • Tipo (Type): tipo de evento ocurrido; en el caso de un evento de ratón, el tipo de evento puede ser botón del ratón presionado, botón del ratón soltado…

Fases del manejo de eventos.

Fase de captura de eventos

Después de la construcción de la cadena de distribución de eventos, el nodo raíz de la aplicación distribuye el evento. Este evento viaja a todos los nodos de la cadena de distribución (de arriba a abajo). Si alguno de estos nodos tiene un filtro registrado para el evento generado, se ejecutará. Si ninguno de los nodos en la cadena de distribución tiene un filtro para el evento generado, entonces se pasa al nodo de destino y finalmente el nodo de destino procesa el evento.

En el ejemplo anterior, si hacemos clic en botón de play, la ruta que se seguiría en esta fase sería:

Stage→Scene→Group→Play Button

Fase de burbujeo del evento

En la fase de burbujeo de eventos, el evento viaja desde el nodo de destino al nodo de etapa (de abajo hacia arriba). Si alguno de los nodos de la cadena de distribución de eventos tiene un controlador registrado para el evento generado, se ejecutará. Si ninguno de estos nodos tiene manejadores para manejar el evento, entonces el evento llega al nodo raíz y finalmente se completará el proceso.

En el ejemplo anterior, si hacemos clic en botón de play, la ruta que se seguiría en esta fase sería:

Play Button→Group→Scene→Stage

Controladores y filtros de eventos

Los filtros y controladores de eventos son aquellos que contienen lógica de aplicación para procesar un evento. Un nodo puede registrarse en más de un controlador/filtro. En el caso de los nodos padre-hijo, se puede proporcionar un filtro/controlador común a los padres, que se procesa de forma predeterminada para todos los nodos secundarios.

Los filtros de eventos se llaman antes que los controladores de eventos. Como se mencionó anteriormente, durante el evento, el procesamiento es un filtro que se ejecuta y durante la fase de propagación del evento, se ejecuta un controlador. Todos los manejadores y filtros implementan la interfaz EventHandler del paquete javafx.event.

Crear filtros y controladores de eventos es tan simple como crear objetos de la clase que implementan la interfaz EventHandler. Antes de Java 8, usaba clases internas para crear controladores y filtros de eventos, como en el siguiente código:

EventHandler<MouseEvent> aHandler = new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent e) {
        // Aquí metemos el código que queremos ejecutar en respuesta al evento
    }
};

A partir de Java 8, el uso de expresiones lambda es la mejor opción para crear los filtros y controladores de eventos, como en el siguiente código:

EventHandler<MouseEvent> aHandler = e -> {
    // El código de manejo del evento va aquí
};

Creación y registro de un EventFilter.

En el siguiente ejemplo se crea un EventFilter que se activará cuando se haga clic en un botón. Cuando se active el filtro se llamará al manejador de eventos llamado handler. Este manejador de eventos imprimirá un mensaje por pantalla y cambiará el color del botón. Para este ejemplo se utiliza la sintaxis “clásica” previa al uso de expresiones lambda.

// Creación de un manejador de evento
EventHandler<MouseEvent> handler = new EventHandler<MouseEvent>() {
    @Override
    public void handle(MouseEvent e) {
        System.out.println("Hello World");
        boton.setFill(Color.DARKSLATEBLUE);
    }
};

// Creación de un EventFilter
boton.addEventFilter(MouseEvent.MOUSE_CLICKED, handler);

El siguiente código muestra el mismo ejemplo, pero gestionando el evento mediante un EventHandler, en vez de un EventFilter. En este ejemplo se utiliza una expresión lambda para escribir el código del manejador

// Creación de un manejador de evento
EventHandler<MouseEvent> handler = e -> {
    System.out.println("Hello World");
    boton.setFill(Color.DARKSLATEBLUE);
};

// Creación de un EventHandler
boton.addEventHandler(MouseEvent.MOUSE_CLICKED, handler);

Eventos de ratón.

Un evento de ratón está representado por la clase MouseEvent.

  • ANY: Es el supertipo de todos los tipos de eventos de ratón. Si un nodo desea recibir todos los tipos de eventos de ratón, debe registrar controladores para este tipo. InputEvent.ANY es el supertipo de este tipo de evento.
  • MOUSE_PRESSED: presionar un botón del ratón genera este evento. El método getButton() de la clase MouseEvent devuelve el botón del mouse que es responsable del evento. Un botón del ratón está representado por las constantes NONE, PRIMARY, MIDDLE y SECONDARY definidas en la enumeración MouseButton.
  • MOUSE_RELEASED: Soltar un botón del ratón genera este evento. Este evento se envía al mismo nodo en el que se presionó el ratón. Por ejemplo, puede presionar un botón del ratón en un círculo, arrastrar el ratón fuera del círculo y soltar el botón del ratón. El evento MOUSE_RELEASED se enviará al círculo, no al nodo en el que se soltó el botón del ratón.
  • MOUSE_CLICKED: este evento se genera cuando se hace clic con un botón del ratón en un nodo. El botón debe presionarse y soltarse en el mismo nodo para que ocurra este evento.
  • MOUSE_MOVED: Mover el ratón sin presionar ningún botón genera este evento.
  • MOUSE_ENTERED: Este evento se genera cuando el ratón ingresa a un nodo. La captura de eventos y las fases de propagación no tienen lugar para este evento. Es decir, los filtros de eventos y los controladores de los nodos principales del destino del evento de este evento no son llamados.
  • MOUSE_ENTERED_TARGET: Este evento se genera cuando el ratón ingresa a un nodo. Es una variante del tipo de evento MOUSE_ENTERED. A diferencia del evento MOUSE_ENTERED, la captura de eventos y las fases de propagación tienen lugar para este evento.
  • MOUSE_EXITED_TARGET: este evento se genera cuando el ratón sale de un nodo. Es una variante del tipo de evento MOUSE_EXITED. A diferencia del evento MOUSE_EXITED, la captura de eventos y las fases de propagación tienen lugar para este evento.
  • DRAG_DETECTED: este evento se genera cuando se presiona el ratón y se arrastra sobre un nodo sobre un umbral de distancia específico.
  • MOUSE_DRAGGED: Mover el ratón con un botón presionado genera este evento. Este evento se envía al mismo nodo en el que se presionó el botón del ratón, independientemente de la ubicación del puntero del ratón durante el arrastre.

Eventos de teclado.

Un evento de ratón está representado por la clase KeyEvent. A continuación, se enumeran todas las constantes de la clase KeyEvent, que representan tipos de eventos de teclado:

  • ANY es el supertipo de todos.
  • KEY_PRESSED ocurre cuando se presiona una Tecla.
  • KEY_RELEASED cuando la Tecla es soltada.
  • KEY_TYPED cuando se teclea un carácter Unicode.

El método getCode() devuelve una constante de enumeración KeyCode que está asociada con la tecla que se presiona o suelta. Es válido para los eventos KEY_PRESSED y KEY_RELEASED. Para el evento KEY_TYPED se puede utilizar el método getCharacter() que devuelve un String con la representación de las teclas pulsadas.

Eventos de ventana.

Un evento de ventana ocurre cuando una ventana se muestra, oculta o cierra. Una instancia de la clase WindowEvent en el paquete javafx.stage representa un evento de ventana. Las constantes de la clase WindowEvent son:

  • ANY es el supertipo de todos los eventos de ventana.
  • WINDOW_SHOWING ocurre justo antes de que se muestre la ventana.
  • WINDOW_SHOWN ocurre justo después de que se muestre la ventana.
  • WINDOW_HIDING ocurre justo antes de que se oculte la Ventana.
  • WINDOW_HIDDEN ocurre justo después de que se oculte la Ventana.
  • WINDOW_CLOSE_REQUEST Se produce cuando hay una solicitud externa para cerrar esta ventana.

Eventos mediante métodos de conveniencia.

Existe la posibilidad de registrar controladores (no de filtros) de algunos eventos específicos mediante el uso de métodos de conveniencia. Estos métodos tienen el nombre setOnXXX(), donde XXX hace referencia al evento concreto.

Por ejemplo, el método setOnMouseClicked() registra un controlador de eventos para el evento en el que se hace clic con el ratón, mientras que el método setOnKeyTyped() lo hace para un evento de tipo key (teclado).

// Registra un controlador de evento para un evento de clic del ratón. 
botonx.setOnMouseClicked(handler);

Resumen de eventos

Hay una gran variedad, y es recomendable investigarlos, a continuación se muestra un resumen.

Evento Descripción Ejemplo de Uso
ActionEvent Ocurre cuando se realiza una acción en un control (ej. botón). button.setOnAction(e -> System.out.println("Botón presionado!"));
MouseEvent Se activa cuando ocurre un evento del ratón (clic, mover). node.setOnMouseClicked(e -> System.out.println("Clic detectado!"));
KeyEvent Se produce cuando se presiona, suelta o escribe una tecla. scene.setOnKeyPressed(e -> System.out.println("Tecla presionada: " + e.getCode()));
ScrollEvent Generado cuando se usa la rueda de desplazamiento del ratón. node.setOnScroll(e -> System.out.println("Scroll detectado!"));
DragEvent Ocurre durante operaciones de arrastrar y soltar. node.setOnDragDropped(e -> System.out.println("Elemento soltado!"));
FocusEvent Se genera cuando un nodo gana o pierde el foco. textField.focusedProperty().addListener((obs, oldVal, newVal) -> System.out.println("Foco cambiado!"));
TouchEvent Detecta toques en dispositivos táctiles. node.setOnTouchPressed(e -> System.out.println("Pantalla táctil presionada!"));
ContextMenuEvent Generado al abrir un menú contextual (botón derecho). node.setOnContextMenuRequested(e -> System.out.println("Menú contextual solicitado!"));
WindowEvent Ocurre durante eventos de la ventana (abrir, cerrar, enfocar). stage.setOnCloseRequest(e -> System.out.println("Ventana cerrándose!"));
InputEvent Clase base para eventos de entrada (ratón, teclado, etc.). node.addEventFilter(InputEvent.ANY, e -> System.out.println("Evento de entrada detectado!"));
ChangeEvent Indica cambios en un estado observable. slider.valueProperty().addListener((obs, oldVal, newVal) -> System.out.println("Valor cambiado: " + newVal));