Introducción a JavaFx

1. Introducción

JavaFX es una biblioteca para crear aplicaciones cliente enriquecidas con Java. Proporciona una API para diseñar aplicaciones GUI que se ejecutan en casi todos los dispositivos compatibles con Java.

En este tutorial, nos centraremos y cubriremos algunas de sus capacidades y funciones clave.

2. API JavaFX

En Java 8, 9 y 10 no es necesaria ninguna configuración adicional para comenzar a trabajar con la biblioteca JavaFX. El proyecto se eliminará del JDK a partir de JDK 11.

2.1. Arquitectura

JavaFX utiliza una canalización de gráficos acelerados por hardware para la representación, conocida como Prism . Además, para acelerar por completo el uso de gráficos, aprovecha el mecanismo de renderizado de software o hardware, mediante el uso interno de DirectX y OpenGL .

JavaFX tiene una capa de kit de herramientas de ventanas Glass dependiente de la plataforma para conectarse al sistema operativo nativo . Utiliza la cola de eventos del sistema operativo para programar el uso de subprocesos. Además, maneja de forma asincrónica ventanas, eventos, temporizadores.

Los motores de medios y web permiten la reproducción de medios y la compatibilidad con HTML / CSS.

Veamos cómo se ve la estructura principal de una aplicación JavaFX:

Aquí, notamos dos contenedores principales:

  • El escenario es el contenedor principal y el punto de entrada de la aplicación . Representa la ventana principal y se pasa como argumento delmétodo start () .
  • Scene es un contenedor para contener los elementos de la interfaz de usuario, como vistas de imagen, botones, cuadrículas, cuadros de texto.

La escena se puede reemplazar o cambiar a otra escena . Esto representa un gráfico de objetos jerárquicos, que se conoce como gráfico de escena . Cada elemento de esa jerarquía se llama nodo. Un solo nodo tiene su ID, estilo, efectos, controladores de eventos, estado.

Además, la escena también contiene los contenedores de diseño, imágenes, medios.

2.2. Hilos

A nivel del sistema, la JVM crea subprocesos separados para ejecutar y representar la aplicación :

  • Hilo de renderizado de prisma : responsable de renderizar el gráfico de escena por separado.
  • Hilo de la aplicación: es el hilo principal de cualquier aplicación JavaFX. Todos los nodos y componentes activos se adjuntan a este hilo.

2.3. Ciclo vital

La clase javafx.application.Application tiene los siguientes métodos de ciclo de vida:

  • init () : se llama después de que se crea la instancia de la aplicación . En este punto, la API de JavaFX aún no está lista, por lo que no podemos crear componentes gráficos aquí.
  • start (Stage stage) : aquí se crean todos los componentes gráficos. Además, el hilo principal de las actividades gráficas comienza aquí.
  • stop () : se llama antes del cierre de la aplicación; por ejemplo, cuando un usuario cierra la ventana principal. Es útil anular este método para realizar una limpieza antes de la finalización de la aplicación.

El método de lanzamiento estático () inicia la aplicación JavaFX.

2.4. FXML

JavaFX utiliza un lenguaje de marcado FXML especial para crear las interfaces de vista.

Esto proporciona una estructura basada en XML para separar la vista de la lógica empresarial. XML es más adecuado aquí, ya que puede representar de forma bastante natural una jerarquía de gráficos de escena .

Finalmente, para cargar el archivo .fxml , usamos la clase FXMLLoader , que da como resultado el gráfico de objetos de la jerarquía de escenas.

3. Introducción

Para ser prácticos, creemos una pequeña aplicación que permite buscar en una lista de personas.

Primero, agreguemos una clase modelo Person para representar nuestro dominio:

public class Person { private SimpleIntegerProperty id; private SimpleStringProperty name; private SimpleBooleanProperty isEmployed; // getters, setters }

Observe cómo, para resumir los valores int, String y booleanos , estamos usando las clases SimpleIntegerProperty, SimpleStringProperty, SimpleBooleanProperty en el paquete javafx.beans.property .

A continuación, creemos la clase Main que amplía la clase abstracta de la aplicación :

public class Main extends Application { @Override public void start(Stage primaryStage) throws Exception { FXMLLoader loader = new FXMLLoader( Main.class.getResource("/SearchController.fxml")); AnchorPane page = (AnchorPane) loader.load(); Scene scene = new Scene(page); primaryStage.setTitle("Title goes here"); primaryStage.setScene(scene); primaryStage.show(); } public static void main(String[] args) { launch(args); } }

Nuestra clase principal anula el método start () , que es el punto de entrada del programa.

Luego, FXMLLoader carga la jerarquía del gráfico de objetos de SearchController.fxml en AnchorPane .

Después de iniciar una nueva escena , la configuramos en la etapa principal . También establecemos el título de nuestra ventana y lo mostramos () .

Tenga en cuenta que es útil incluir el método main () para poder ejecutar el archivo JAR sin JavaFX Launcher .

3.1. Vista FXML

Ahora profundicemos en el archivo XML de SearchController .

Para nuestra aplicación de búsqueda, agregaremos un campo de texto para ingresar la palabra clave y el botón de búsqueda:

AnchorPane es el contenedor raíz aquí y el primer nodo de la jerarquía del gráfico. Mientras cambia el tamaño de la ventana, reposicionará al niño en su punto de anclaje. El atributo fx: controller conecta la clase Java con el marcado.

Hay otros diseños integrados disponibles:

  • BorderPane : divide el diseño en cinco secciones: superior, derecha, inferior, izquierda, centro
  • HBox: organiza los componentes secundarios en un panel horizontal
  • VBox: los nodos secundarios están dispuestos en una columna vertical
  • GridPane: útil para crear una cuadrícula con filas y columnas

In our example, inside of the horizontal HBox panel, we used a Label to place text, TextField for the input, and a Button. With fx: id we mark the elements so that we can use them later in the Java code.

The VBox panel is where we'll display the search results.

Then, to map them to the Java fields – we use the @FXML annotation:

public class SearchController { @FXML private TextField searchField; @FXML private Button searchButton; @FXML private VBox dataContainer; @FXML private TableView tableView; @FXML private void initialize() { // search panel searchButton.setText("Search"); searchButton.setOnAction(event -> loadData()); searchButton.setStyle("-fx-background-color: #457ecd; -fx-text-fill: #ffffff;"); initTable(); } }

After populating the @FXML annotated fields, initialize() will be called automatically. Here, we're able to perform further actions over the UI components – like registering event listeners, adding style or changing the text property.

In the initTable() method we'll create the table that will contain the results, with 3 columns, and add it to the dataContainer VBox:

private void initTable() { tableView = new TableView(); TableColumn id = new TableColumn("ID"); TableColumn name = new TableColumn("NAME"); TableColumn employed = new TableColumn("EMPLOYED"); tableView.getColumns().addAll(id, name, employed); dataContainer.getChildren().add(tableView); }

Finally, all of this logic described here will produce the following window:

4. Binding API

Now that the visual aspects are handled, let's start looking at binding data.

The binding API provides some interfaces that notify objects when a value change of another object occurs.

We can bind a value using the bind() method or by adding listeners.

Unidirectional binding provides a binding for one direction only:

searchLabel.textProperty().bind(searchField.textProperty());

Here, any change in the search field will update the text value of the label.

By comparison, bidirectional binding synchronizes the values of two properties in both directions.

The alternative way of binding the fields are ChangeListeners:

searchField.textProperty().addListener((observable, oldValue, newValue) -> { searchLabel.setText(newValue); });

The Observable interface allows observing the value of the object for changes.

To exemplify this, the most commonly used implementation is the javafx.collections.ObservableList interface:

ObservableList masterData = FXCollections.observableArrayList(); ObservableList results = FXCollections.observableList(masterData);

Here, any model change like insertion, update or removal of the elements, will notify the UI controls immediately.

The masterData list will contain the initial list of Person objects, and the results list will be the list we display upon searching.

We also have to update the initTable() method to bind the data in the table to the initial list, and to connect each column to the Person class fields:

private void initTable() { tableView = new TableView(FXCollections.observableList(masterData)); TableColumn id = new TableColumn("ID"); id.setCellValueFactory(new PropertyValueFactory("id")); TableColumn name = new TableColumn("NAME"); name.setCellValueFactory(new PropertyValueFactory("name")); TableColumn employed = new TableColumn("EMPLOYED"); employed.setCellValueFactory(new PropertyValueFactory("isEmployed")); tableView.getColumns().addAll(id, name, employed); dataContainer.getChildren().add(tableView); }

5. Concurrency

Working with the UI components in a scene graph isn't thread-safe, as it's accessed only from the Application thread. The javafx.concurrent package is here to help with multithreading.

Let's see how we can perform the data search in the background thread:

private void loadData() { String searchText = searchField.getText(); Task
    
      task = new Task
     
      () { @Override protected ObservableList call() throws Exception { updateMessage("Loading data"); return FXCollections.observableArrayList(masterData .stream() .filter(value -> value.getName().toLowerCase().contains(searchText)) .collect(Collectors.toList())); } }; }
     
    

Here, we create a one-time task javafx.concurrent.Task object and override the call() method.

The call() method runs entirely on the background thread and returns the result to the Application thread. This means any manipulation of the UI components within this method, will throw a runtime exception.

However, updateProgress(), updateMessage() can be called to update Application thread items. When the task state transitions to SUCCEEDED state, the onSucceeded() event handler is called from the Application thread:

task.setOnSucceeded(event -> { results = task.getValue(); tableView.setItems(FXCollections.observableList(results)); }); 

In the same callback, we've updated the tableView data to the new list of results.

The Task is Runnable, so to start it we need just to start a new Thread with the task parameter:

Thread th = new Thread(task); th.setDaemon(true); th.start();

The setDaemon(true) flag indicates that the thread will terminate after finishing the work.

6. Event Handling

We can describe an event as an action that might be interesting to the application.

For example, user actions like mouse clicks, key presses, window resize are handled or notified by javafx.event.Event class or any of its subclasses.

Also, we distinguish three types of events:

  • InputEvent – all the types of key and mouse actions like KEY_PRESSED, KEY_TYPED, KEY_RELEASED or MOUSE_PRESSES, MOUSE_RELEASED
  • ActionEvent – represents a variety of actions like firing a Button or finishing a KeyFrame
  • WindowEventWINDOW_SHOWING, WINDOW_SHOWN

To demonstrate, the code fragment below catches the event of pressing the Enter key over the searchField:

searchField.setOnKeyPressed(event -> { if (event.getCode().equals(KeyCode.ENTER)) { loadData(); } });

7. Style

Podemos cambiar la interfaz de usuario de la aplicación JavaFX aplicándole un diseño personalizado.

De forma predeterminada, JavaFX usa modena.css como recurso CSS para toda la aplicación. Esto es parte de jfxrt.jar .

Para anular el estilo predeterminado, podemos agregar una hoja de estilo a la escena:

scene.getStylesheets().add("/search.css");

También podemos usar el estilo en línea; por ejemplo, para establecer una propiedad de estilo para un nodo específico:

searchButton.setStyle("-fx-background-color: slateblue; -fx-text-fill: white;");

8. Conclusión

Esta breve reseña cubre los conceptos básicos de la API de JavaFX. Revisamos la estructura interna e introdujimos capacidades clave de su arquitectura, ciclo de vida y componentes.

Como resultado, aprendimos y ahora podemos crear una aplicación GUI simple.

Y, como siempre, el código fuente completo del tutorial está disponible en GitHub.