Commit 0ee48c2e authored by jg5dev's avatar jg5dev 💬
Browse files

better

parent 87dfa51d
Loading
Loading
Loading
Loading
+21 −6
Changes for src/java/uf5/ud3.md: 21 added lines, 6 removed lines.
Original line number Diff line number Diff line
# Interfícies gràfiques

Una aplicació amb interfície gràfica no s'executa de dalt a baix: espera. El flux el marquen les accions de l'usuari, i el programa es converteix en un conjunt de respostes a esdeveniments. Aquesta unitat explica aquest model i el construeix amb **JavaFX**, la llibreria gràfica de Java.

- [Programació orientada a esdeveniments](./ud3/poe.md): el bucle d'esdeveniments, el patró observador i els callbacks.
- [JavaFX](./ud3/javafx.md): què és, com afegir-la al projecte i el model Stage, Scene i Node.
- [Tipus de nodes](./ud3/tipus_nodes.md): controls, contenidors, figures i el dibuix amb `Canvas`.
- [Aplicació](./ud3/aplicacio.md): l'estructura mínima d'una aplicació i el seu cicle de vida.
- [Scene Graph](./ud3/scene_graph.md): l'arbre de nodes i els layouts que els col·loquen.
- [FXML](./ud3/fxml.md): definir la interfície en un fitxer XML i connectar-la amb un controlador.
- [Múltiples finestres](./ud3/multi_finestres.md): obrir finestres, modalitat i diàlegs.
- [Gestió d'esdeveniments](./ud3/esdeveniments.md): com viatja un esdeveniment pel graf i com se'l captura.
- [Patró de disseny UI](./ud3/patro_ui.md): MVC i MVP, i com separar la vista del model.
- [Concurrència](./ud3/concurrencia.md): per què la interfície es congela i com evitar-ho amb `Task`.
- [Binding de propietats](./ud3/binding.md): mantenir sincronitzats el model i la interfície.

## Referències

*   [JavaFX 25 Javadoc](https://openjfx.io/javadoc/25/)
*   [GUI Architectures (Martin Fowler)](https://martinfowler.com/eaaDev/uiArchs.html)
*   [The Observer Pattern Using Java 8](https://dzone.com/articles/the-observer-pattern-using-modern-java)
*   [Observer vs Pub-Sub pattern](https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c)
*   [MVC Patterns](http://griffon-framework.org/tutorials/5_mvc_patterns.html)
*   [Callbacks](https://web.mit.edu/6.031/www/fa20/classes/25-callbacks/)
- [JavaFX 25 Javadoc](https://openjfx.io/javadoc/25/)
- [OpenJFX: Getting Started](https://openjfx.io/openjfx-docs/)
- [GUI Architectures (Martin Fowler)](https://martinfowler.com/eaaDev/uiArchs.html)
- [The Observer Pattern Using Java 8](https://dzone.com/articles/the-observer-pattern-using-modern-java)
- [Observer vs Pub-Sub pattern](https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c)
- [MVC Patterns](http://griffon-framework.org/tutorials/5_mvc_patterns.html)
- [Callbacks](https://web.mit.edu/6.031/www/fa20/classes/25-callbacks/)
+57 −10
Changes for src/java/uf5/ud3/aplicacio.md: 57 added lines, 10 removed lines.
Original line number Diff line number Diff line
@@ -2,25 +2,72 @@

<!-- toc -->

Ara veure'm un exemple mínim d'aplicació. Tenim els següents components gràfics:

*   Stage: la finestra principal
*   Scene: el contenidor del graf d'elements gràfics
*   Graf scene: la jerarquia d'elements gràfics, en aquest exemple: Label.
Aquesta és una aplicació JavaFX completa: una finestra amb un text a dins.

```java
public class JavaWorldApp extends Application {  
public class HelloApp extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception { 
        // creació del stage, scene i scene graph
        primaryStage.setTitle("Hello world App");
    public void start(Stage primaryStage) {
        Label label = new Label("Hello World!");
        Scene scene = new Scene(label, 400, 200);

        primaryStage.setTitle("Hello world App");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    public static void main(String args[]){           

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

Hi són les tres peces de què parlàvem: el `Stage` és la finestra, la `Scene` el contenidor del graf, i el `Label` és, en aquest cas, tot el graf.

## Què passa quan s'engega

El `main()` només crida `launch()`, i a partir d'aquí mana JavaFX. El mètode `launch()` engega el sistema gràfic, crea el **JavaFX Application Thread**, instancia la teva classe i li crida els mètodes del cicle de vida:

| Mètode | Quan es crida | On s'executa |
| --- | --- | --- |
| `init()` | Un cop, abans de la interfície | Al fil que ha cridat `launch()` |
| `start(Stage)` | Un cop, amb la finestra principal ja creada | Al JavaFX Application Thread |
| `stop()` | En tancar l'última finestra o cridar `Platform.exit()` | Al JavaFX Application Thread |

Només `start()` és obligatori. Els altres dos es redefineixen quan cal:

```java
@Override
public void init() {
    // preparar dades o carregar configuració, encara sense interfície
}

@Override
public void stop() {
    // tancar connexions, desar l'estat
}
```

**A `init()` no es pot tocar la interfície**, perquè encara no existeix i perquè no s'executa al fil de JavaFX. Tot el que construeixi nodes va a `start()`.

`launch()` és bloquejant: no retorna fins que l'aplicació acaba. Per això no hi ha res després de la seva crida al `main()`.

## El main que no cal

Amb la configuració de Maven que hem vist, el plugin ja sap quina és la classe principal i el `main()` no és estrictament necessari. Es continua escrivint per dos motius: perquè molts IDE l'esperen per oferir el botó d'executar, i perquè permet passar arguments.

Els arguments es recuperen des de dins de l'aplicació amb `getParameters()`:

```java
List<String> args = getParameters().getRaw();
```

## Tancar l'aplicació

Per defecte, l'aplicació acaba quan es tanca l'última finestra. Es pot forçar amb `Platform.exit()`, que crida `stop()` i allibera el sistema gràfic. Convé evitar `System.exit()`, que talla el procés sense passar per `stop()`.

## Referències

* [Application (JavaFX 25)](https://openjfx.io/javadoc/25/javafx.graphics/javafx/application/Application.html)
* [Platform (JavaFX 25)](https://openjfx.io/javadoc/25/javafx.graphics/javafx/application/Platform.html)
+55 −7
Changes for src/java/uf5/ud3/concurrencia.md: 55 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -2,19 +2,67 @@

<!-- toc -->

Les aplicacions GUI (interfície gràfica d'usuari) de Java (inclosa JavaFX) són inherentment multifil. Diversos fils realitzen tasques diferents per mantenir la interfície d'usuari en sincronització amb les accions de l'usuari. JavaFX utilitza **un únic fil**, anomenat **JavaFX Application Thread**, per processar tots els esdeveniments de la interfície d'usuari. Els nodes que representen la interfície d'usuari d'una gràfica d'escena no són segurs. El disseny de nodes que no són segurs per a fils presenta avantatges i inconvenients. Són més ràpids, ja que no hi ha cap sincronització. L’inconvenient és que s’han d’accedir des d’un mateix fil per evitar estar en un estat il·legal. JavaFX posa una restricció a la qual s’ha d’accedir a un gràfic d’escena en directe des d’un únic fil, el fil d’aplicacions JavaFX. Aquesta restricció imposa indirectament una altra restricció que un esdeveniment d’UI **no ha de processar una tasca de llarga durada**, ja que farà que l’aplicació no respongui.
JavaFX processa tots els esdeveniments de la interfície en un únic fil, el **JavaFX Application Thread**. Aquest fil és el que crida els teus gestors d'esdeveniments i el que repinta la pantalla.

![](../../images/jfxconcurrency.png)
D'aquí surten les dues regles que governen tota aplicació gràfica:

Si un altre fil vol modificar la GUI, cal que utilitzi la següent construcció per afegir la tasca a la cua d'esdeveniments:
* **Només aquest fil pot tocar un graf d'escena que ja es mostra.** Els nodes no són thread-safe, i és una decisió deliberada: sense sincronització, el dibuix és molt més ràpid. El preu és que accedir-hi des d'un altre fil deixa la interfície en un estat inconsistent, sovint amb una excepció.
* **Aquest fil no pot fer feina llarga.** Mentre executa el teu codi no processa esdeveniments ni repinta, de manera que l'aplicació es queda congelada. Una consulta a una base de dades o una descàrrega dins d'un gestor de botó és exactament això.

![Fils d'una aplicació JavaFX](../../images/jfxconcurrency.png)

La sortida és la de sempre: la feina llarga va a un altre fil, i el resultat torna al fil de JavaFX per mostrar-lo. Els conceptes de fons són els de [Processos i fils](../../../m9/uf2.md).

## Platform.runLater

`Platform.runLater()` posa una tasca a la cua del fil de JavaFX. És el mecanisme bàsic per actualitzar la interfície des d'un fil de fons:

```java
new Thread(() -> {
    String resultat = consultaLlarga();          // fil de fons
    Platform.runLater(() -> label.setText(resultat));   // fil de JavaFX
}).start();
```

Funciona, però deixa la feina de coordinació a les teves mans: saber si la tasca ha acabat, mostrar el progrés, cancel·lar-la o recollir l'excepció que hagi llançat.

## Task

El paquet `javafx.concurrent` resol precisament això. Un [`Task<V>`](https://openjfx.io/javadoc/25/javafx.graphics/javafx/concurrent/Task.html) és una feina que s'executa en un fil de fons i que exposa el seu estat com a **propietats observables**, ja preparades per lligar-les a la interfície.

Platform.runLater(new Runnable() {
```java
Task<String> tasca = new Task<>() {
    @Override
    public void run() {
        // acció que es vol realitzar
    protected String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            updateProgress(i, 100);
            updateMessage("Processant " + i);
            Thread.sleep(50);
        }
        return "fet";
    }
});
};

progressBar.progressProperty().bind(tasca.progressProperty());
label.textProperty().bind(tasca.messageProperty());
tasca.setOnSucceeded(e -> mostraResultat(tasca.getValue()));
tasca.setOnFailed(e -> mostraError(tasca.getException()));

Thread fil = new Thread(tasca);
fil.setDaemon(true);
fil.start();
```

El mètode `call()` s'executa al fil de fons. La resta ja el gestiona JavaFX:

* `updateProgress()`, `updateMessage()` i `updateValue()` es poden cridar des de `call()` i publiquen el valor al fil de JavaFX. **No cal `runLater()`.**
* Els gestors `setOnSucceeded()`, `setOnFailed()` i `setOnCancelled()` s'executen al fil de JavaFX, de manera que hi pots tocar la interfície directament.
* `cancel()` marca la tasca, i el `call()` ho ha de comprovar amb `isCancelled()` per aturar-se de veritat.

Un `Task` és d'un sol ús. Si la feina s'ha de poder repetir, com un refresc periòdic, el que vols és un [`Service`](https://openjfx.io/javadoc/25/javafx.graphics/javafx/concurrent/Service.html): fabrica un `Task` nou cada vegada i es pot arrencar i reiniciar amb `start()` i `restart()`.

## Referències

* [Task (JavaFX 25)](https://openjfx.io/javadoc/25/javafx.graphics/javafx/concurrent/Task.html)
* [Service (JavaFX 25)](https://openjfx.io/javadoc/25/javafx.graphics/javafx/concurrent/Service.html)
* [Platform (JavaFX 25)](https://openjfx.io/javadoc/25/javafx.graphics/javafx/application/Platform.html)
+1 −1
Changes for src/java/uf5/ud3/esdeveniments.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -89,4 +89,4 @@ I per escoltar un índex numèric sobre un `ChoiceBox`:

*   `ChoiceBox.getSelectionModel().selectedIndexProperty().addListener(ChangeListener listener)`

Pots veure la [llista de controls i com utilitzar-los](https://docs.oracle.com/javase/8/javafx/user-interface-tutorial/ui_controls.htm).
 No newline at end of file
Pots veure la [llista de controls i els seus mètodes](https://openjfx.io/javadoc/25/javafx.controls/javafx/scene/control/package-summary.html).
 No newline at end of file
+1 −1
Changes for src/java/uf5/ud3/fxml.md: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -2,7 +2,7 @@

<!-- toc -->

A l'hora de crear elements gràfics tenim dues opcions: crear-los programàticament o bé amb un arxiu de tipus XML anomenat FXML. El format FXML facilita el dibuix mitjançant eines de disseny com el [Scene Builder](https://www.oracle.com/java/technologies/javase/javafxscenebuilder-info.html). A més, permet associar el codi XML amb el codi Java:
A l'hora de crear elements gràfics tenim dues opcions: crear-los programàticament o bé amb un arxiu de tipus XML anomenat FXML. El format FXML facilita el dibuix mitjançant eines de disseny com el [Scene Builder](https://gluonhq.com/products/scene-builder/). A més, permet associar el codi XML amb el codi Java:

*   Cal definir un controlador, un objecte Java que serà el nexe de comunicació del món XML i el món Java. Aquest ha d'implementar la interfície javafx.fxml.Initializable.
*   Defineix associacions entre objectes al FXML (propietat `fx:id` dels elements) i objectes Java del controlador.
Loading