Gestione rettangolo in JavaFX

Vediamo un esercizio semplice ma sempre indicativo a livello didattico per il calcolo di informazioni geometriche di un rettangolo. L’idea è di consentire con una form banale di due campi, di inserire i lati di un rettangolo mostrando in una porzione del finestra il calcolo di area, perimetro, diagonale. Disegniamo anche un ipotetico rettangolo dove alle etichette lato lungo/corto sostituiamo le dimensioni scelte dall’utente.

esempio di soluzione gestione rettangolo javafx

La classe di gestione/modello

Come buona prassi, ogni volta che dobbiamo implementare un software con GUI, dobbiamo partire dal presupposto che il problema lo deve risolvere una classe, o più classi, standard, quelle normali prive dell’approccio grafico, per intenderci. Questa o queste classi sono le classi di Modello nell’ambito del sistema MVC, che contengono i dati di interesse del problema più quanto serve alla loro manipolazione. Nel nostro caso è facile individuare la classe con gli attributi necessari: una classica classe rettangolo già implementata anche in esercizi precedenti. Riportiamo una versione qui sotto. L’alunno alle prime armi è portato a risolvere il problema sempre all’interno delle strutture e metodi della GUI, ma si deve fare un piccolo sforzo di astrazione e delegare alla GUI poche funzionalità legate solo al click e gestione degli eventi, non del codice vero e proprio.

La nostra classe ha due semplici attributi per i lati, i metodi di accesso, e i metodi accessori per calcolare le grandezze richieste, perimetro, area diagonale. Non commentiamo ulteriormente perché sono decisamente semplici. Badate bene: non stampano nulla, tornano valori calcolati che verranno invece visualizzati dalla GUI.

package itt.rettangolofx;

public class Rettangolo
{
    private double latoLungo;
    private double latoCorto;

    public Rettangolo(double latoLungo, double latoCorto) {
        this.latoLungo = latoLungo;
        this.latoCorto = latoCorto;
    }

    public double getLatoLungo() {
        return latoLungo;
    }

    public void setLatoLungo(double latoLungo) {
        this.latoLungo = latoLungo;
    }

    public double getLatoCorto() {
        return latoCorto;
    }

    public void setLatoCorto(double latoCorto) {
        this.latoCorto = latoCorto;
    }

    public double area()
    {
        return this.latoCorto * this.latoLungo;
    }

    public double perimetro()
    {
        return 2*(this.latoCorto + this.latoLungo);
    }

    public double diagonale()
    {
        double pitagora = Math.pow(this.latoCorto,2) + Math.pow(this.latoLungo,2);
        return  Math.sqrt(pitagora);
    }
}

Il design

Con lo SceneBuilder approcciamo ad un design del nostro software. Ovviamente non c’è una sola soluzione. Scegliamo di utilizzare una VBox e inserire al suo interno trelivelli: il primo con un Pane a “mano libera” dove inserirei due campi di input, una label con cui disegnare il rettangolo colando i bordi di rosso e due etichette per le diciture lato lungo/corto; quindi una HBox per allineare i bottini Calcola e Cancella; terzo livello una TextArea dove visualizzare i risultati attesi.

Obbligatorio, scelti i componenti, è etichettarli con un ID, qualora tali componenti debbano realzionarsi col software o con l’utente. Nel nostro caso tutti gli elementi aggiunti bottoni, textarea, le etichette lato lungo/corto, i campi della form richiedono un ID che li identifichi poi nel codice. Rimangono solo le label della form e il rettangolo rosso che non prevediamo debbano essere modificati o interagire col software.

Dopo aver scelto gli ID dobbiamo scegliere la gestione degli eventi. qui in realtà abbiamo solo due bottoni con i rispettivi eventi click onAction a cui assegniamo un nominativo per le funzioni che successivamente gestiranno il loro codice/funzionamento.

Il codice del file FXML risultante che potete eventualmetne reimporstare/incollare nel vostro progetto. Assicuratevi sempre che sia specificata la classe Controller o da SceneBuilder sulla barra sinistra o dal file FXML cercando l’attributo fx:controller.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>

<VBox alignment="CENTER" minHeight="400.0" minWidth="600.0" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/19" fx:controller="itt.rettangolofx.Controller">
   <children>
      <Pane prefHeight="200.0" prefWidth="200.0">
         <children>
            <Label layoutX="42.0" layoutY="63.0" text="Lato lungo" />
            <Label layoutX="42.0" layoutY="116.0" text="Lato corto" />
            <TextField fx:id="txtLatolungo" layoutX="136.0" layoutY="59.0" />
            <TextField fx:id="txtLatocorto" layoutX="136.0" layoutY="112.0" />
            <Label layoutX="373.0" layoutY="61.0" prefHeight="78.0" prefWidth="161.0" style="-fx-border-color: red;" />
            <Label fx:id="lblLungo" alignment="CENTER" layoutX="439.0" layoutY="142.0" text="lungo" textAlignment="CENTER" />
            <Label fx:id="lblCorto" alignment="CENTER" layoutX="536.0" layoutY="92.0" rotate="90.0" text="corto" textAlignment="CENTER" />
         </children>
      </Pane>
      <HBox alignment="CENTER_RIGHT" prefHeight="62.0" prefWidth="596.0">
         <children>
            <Button fx:id="btnCalcola" minHeight="30.0" minWidth="90.0" mnemonicParsing="false" onAction="#calcola" text="Calcola">
               <HBox.margin>
                  <Insets right="20.0" />
               </HBox.margin>
            </Button>
            <Button fx:id="btnCancella" minHeight="30.0" minWidth="90.0" mnemonicParsing="false" onAction="#cancella" text="Cancella">
               <HBox.margin>
                  <Insets right="20.0" />
               </HBox.margin>
            </Button>
         </children>
      </HBox>
      <TextArea fx:id="txtArea" editable="false" prefHeight="150.0" prefWidth="600.0" text="Inserisci le dimensioni dei due lati lungo e corto per vedere le info del rettangolo qui di seguito" wrapText="true" />
   </children>
</VBox>

La classe Controller

Dallo SceneBuilder, preleviamo lo “Skeleton” della nostra classe Controller e andiamo a inserire il codice dei bottoni. al bottone calcola al suo onAction abbiamo associato la funzione calcola. Per renderla un po’ più interessante inseriamo dei controlli di validità dei dati inseriti dall’utente. Non è obligatorio, ma per creare software di qualità professionale è obbligatorio. Un dato sbagliato, o vuoto, possono far saltare il nostro software/interfaccia o dare risultati inattesi. Qui in reatà ci limitiamo a due semplici controlli. Il primo verifica se i campi lato lungo/corto sono vuoti, esco dal metodo calcola e non procedo a calcolare/visualizzare nulla. Il secondo controllo è leggemente più complesso perché prevede una regex, una espressione regolare per controllare se il dato inserito sia un numero valido e non una lettera o altro segno di punteggiatura. Qui il nostro sviluppatore provetto deve afre un tto di fede e intuiore che “matches(“[0-9]+”)” confronta la stringa di testo del campo e controlla se sono presenti solo cifre da 0 a 9 e da errore di consegunza se nel mach c’è altro. Anche ui, in caso, usciamo dal metodo senza fare nulla. Non fornisco un feedback di ritorno: sarebbe opportuno contrassegnare di rosso il campo sbagliato o segnalare in altro modo l’errore.

A questo punto siamo sicuri che i dati o sono stati inseriti o comunque sono corretti. Posso prelevarli e fornirli in pasto a funzioni di calcolo semplici per area, perimetro, diagonale che abbiamo in realtà già implementato nella classe Rettangolo. Qual è la accortezza da avere? Tra gli attributi preconfezionati del nostro Controller inseriamo un private Rettangolo r o lo definimo/inizializziamo direttamente dentro questo metodo calcola, per poter utilizzare i suoi metodi e costruttore.

La viusalizzzione conseguente i calcoli è decisamente semplice e lasciamo la lettura allo studente.

void calcola(ActionEvent event)
    {
        /**
         * Faccio prima una serie di controlli sulla correttezza dei dati inseriti
         */
        if (this.txtLatocorto.getText().isBlank())
            return;

        if (this.txtLatolungo.getText().isBlank())
            return;

        if (!this.txtLatolungo.getText().matches("[0-9]+"))
            return;

        if (!this.txtLatocorto.getText().matches("[0-9]+"))
            return;

        double latoLungo = Double.valueOf(this.txtLatolungo.getText());
        double latoCorto = Double.valueOf(this.txtLatocorto.getText());

        this.lblLungo.setText(this.txtLatolungo.getText());
        this.lblCorto.setText(this.txtLatocorto.getText());

        r = new Rettangolo(latoLungo, latoCorto);
        this.txtArea.setText( "Area=" + r.area() + "\n" +
                              "Perimetro= " + r.perimetro() + "\n" +
                              "Diagonale= " + r.diagonale()  );

    }

La classe Controller al completo:

package itt.rettangolofx;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;

public class Controller
{

    Rettangolo r;
    @FXML
    private ResourceBundle resources;

    @FXML
    private URL location;

    @FXML
    private Button btnCalcola;

    @FXML
    private Button btnCancella;

    @FXML
    private Label lblCorto;

    @FXML
    private Label lblLungo;

    @FXML
    private TextArea txtArea;

    @FXML
    private TextField txtLatocorto;

    @FXML
    private TextField txtLatolungo;

    @FXML
    void calcola(ActionEvent event)
    {
        /**
         * Faccio prima una serie di controlli sulla correttezza dei dati inseriti
         */
        if (this.txtLatocorto.getText().isBlank())
            return;

        if (this.txtLatolungo.getText().isBlank())
            return;

        if (!this.txtLatolungo.getText().matches("[0-9]+"))
            return;

        if (!this.txtLatocorto.getText().matches("[0-9]+"))
            return;

        double latoLungo = Double.valueOf(this.txtLatolungo.getText());
        double latoCorto = Double.valueOf(this.txtLatocorto.getText());

        this.lblLungo.setText(this.txtLatolungo.getText());
        this.lblCorto.setText(this.txtLatocorto.getText());

        r = new Rettangolo(latoLungo, latoCorto);
        this.txtArea.setText( "Area=" + r.area() + "\n" +
                              "Perimetro= " + r.perimetro() + "\n" +
                              "Diagonale= " + r.diagonale()  );

    }

    @FXML
    void cancella(ActionEvent event)
    {
        this.lblLungo.setText("");
        this.lblCorto.setText("");
        this.txtArea.setText("");
        this.txtLatocorto.setText("");
        this.txtLatolungo.setText("");
    }

    @FXML
    void initialize() {
        assert btnCalcola != null : "fx:id=\"btnCalcola\" was not injected: check your FXML file 'rettangolo-view.fxml'.";
        assert btnCancella != null : "fx:id=\"btnCancella\" was not injected: check your FXML file 'rettangolo-view.fxml'.";
        assert lblCorto != null : "fx:id=\"lblCorto\" was not injected: check your FXML file 'rettangolo-view.fxml'.";
        assert lblLungo != null : "fx:id=\"lblLungo\" was not injected: check your FXML file 'rettangolo-view.fxml'.";
        assert txtArea != null : "fx:id=\"txtArea\" was not injected: check your FXML file 'rettangolo-view.fxml'.";
        assert txtLatocorto != null : "fx:id=\"txtLatocorto\" was not injected: check your FXML file 'rettangolo-view.fxml'.";
        assert txtLatolungo != null : "fx:id=\"txtLatolungo\" was not injected: check your FXML file 'rettangolo-view.fxml'.";

    }

}

La classe Application

Qui non abbiamo, al meomento, particolari elementi da modificare. Accertatevi, oltre al titol della finestra, che la dimensione dalla scena Scene sia coerente con quella dello SceneBuilder o inorrerete in un errore di RunTime sciocco e fastidioso.

package itt.rettangolofx;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class MainApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(MainApplication.class.getResource("rettangolo-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 600, 400);
        stage.setTitle("Gestione Rettangolo");
        stage.setScene(scene);
        stage.show();
    }

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

Ultima modifica 26 Maggio 2023