Ricerca…


Sintassi

Esempio FXML

Un documento FXML semplice che descrive un AnchorPane contenente un pulsante e un nodo etichetta:

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

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" 
        fx:controller="com.example.FXMLDocumentController">
    <children>
        <Button layoutX="126" layoutY="90" text="Click Me!" onAction="#handleButtonAction" fx:id="button" />
        <Label layoutX="126" layoutY="120" minHeight="16" minWidth="69" fx:id="label" />
    </children>
</AnchorPane>

Questo file FXML di esempio è associato a una classe controller. L'associazione tra FXML e la classe controller, in questo caso, viene effettuata specificando il nome della classe come valore dell'attributo fx:controller nell'elemento radice di FXML: fx:controller="com.example.FXMLDocumentController" . La classe controller consente l'esecuzione del codice Java in risposta alle azioni dell'utente sugli elementi dell'interfaccia utente definiti nel file FXML:

package com.example ;

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

public class FXMLDocumentController {
    
    @FXML
    private Label label;
    
    @FXML
    private void handleButtonAction(ActionEvent event) {
        System.out.println("You clicked me!");
        label.setText("Hello World!");
    }
    
    @Override
    public void initialize(URL url, ResourceBundle resources) {
        // Initialization code can go here. 
        // The parameters url and resources can be omitted if they are not needed
    }    
    
}

Un FXMLLoader può essere utilizzato per caricare il file FXML:

public class MyApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("FXMLDocument.fxml"));
        Parent root = loader.load();
        
        Scene scene = new Scene(root);
        
        stage.setScene(scene);
        stage.show();
    }

}

Il metodo di load esegue diverse azioni ed è utile per capire l'ordine in cui si verificano. In questo semplice esempio:

  1. FXMLLoader legge e analizza il file FXML. Crea oggetti corrispondenti agli elementi definiti nel file e prende nota di eventuali attributi fx:id definiti su di essi.

  2. Poiché l'elemento radice del file FXML ha definito un attributo fx:controller , FXMLLoader crea una nuova istanza della classe che specifica. Per impostazione predefinita ciò avviene richiamando il costruttore no-argument sulla classe specificata.

  3. Tutti gli elementi con gli attributi fx:id definiti che hanno campi nel controller con nomi di campi corrispondenti e che sono public (non consigliato) o annotati @FXML (consigliato) vengono "iniettati" in quei campi corrispondenti. Quindi in questo esempio, dal momento che esiste Label nel file FXML con fx:id="label" e un campo nel controller definito come

    @FXML
    private Label label ;
    

    il campo label viene inizializzato con l'istanza Label creata da FXMLLoader .

  4. I gestori di eventi sono registrati con qualsiasi elemento nel file onXXX="#..." con le proprietà onXXX="#..." definite. Questi gestori eventi invocano il metodo specificato nella classe controller. In questo esempio, poiché Button ha onAction="#handleButtonAction" e il controller definisce un metodo

    @FXML
    private void handleButtonAction(ActionEvent event) { ... }
    

    quando un'azione viene attivata sul pulsante (ad esempio, l'utente lo preme), questo metodo viene richiamato. Il metodo deve avere un tipo restituito void e può definire un parametro corrispondente al tipo di evento ( ActionEvent in questo esempio) oppure non può definire alcun parametro.

  5. Infine, se la classe controller definisce un metodo di initialize , questo metodo viene richiamato. Si noti che questo accade dopo che i campi @FXML sono stati iniettati, quindi possono essere tranquillamente utilizzati con questo metodo e verranno inizializzati con le istanze corrispondenti agli elementi nel file FXML. Il metodo initialize() può accettare alcun parametro o può prendere un URL e un ResourceBundle . In quest'ultimo caso, questi parametri verranno popolati URL rappresenta la posizione del file FXML e da qualsiasi set di ResourceBundle su FXMLLoader tramite loader.setResources(...) . Ciascuno di questi può essere null se non sono stati impostati.

Controller annidati

Non è necessario creare l'intera interfaccia utente in un singolo FXML utilizzando un singolo controller.

Il <fx:include> può essere utilizzato per includere un file fxml in un altro. Il controller del fxml incluso può essere iniettato nel controller del file incluso come qualsiasi altro oggetto creato da FXMLLoader .

Questo viene fatto aggiungendo l'attributo fx:id all'elemento <fx:include> . In questo modo il controller del fxml incluso verrà iniettato nel campo con il nome <fx:id value>Controller .

Esempi:

fx: valore id nome del campo per l'iniezione
foo fooController
answer42 answer42Controller
Xyz xYzController

Esempio di fxmls

contatore

Questo è un fxml che contiene uno StackPane con un nodo di Text . Il controller per questo file fxml consente di ottenere il valore del contatore corrente e di incrementare il contatore:

counter.fxml

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

<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>

<StackPane prefHeight="200" prefWidth="200" xmlns:fx="http://javafx.com/fxml/1" fx:controller="counter.CounterController">
    <children>
        <Text fx:id="counter" />
    </children>
</StackPane>

CounterController

package counter;

import javafx.fxml.FXML;
import javafx.scene.text.Text;

public class CounterController {
    @FXML
    private Text counter;

    private int value = 0;
    
    public void initialize() {
        counter.setText(Integer.toString(value));
    }
    
    public void increment() {
        value++;
        counter.setText(Integer.toString(value));
    }
    
    public int getValue() {
        return value;
    }
    
}

Compreso fxml

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

<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<BorderPane prefHeight="500" prefWidth="500" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="counter.OuterController">
    <left>
        <Button BorderPane.alignment="CENTER" text="increment" onAction="#increment" />
    </left>
    <center>
        <!-- content from counter.fxml included here -->
        <fx:include fx:id="count" source="counter.fxml" />
    </center>
</BorderPane>

OuterController

Il controller del fxml incluso viene iniettato su questo controller. Qui il gestore dell'evento onAction per il Button viene utilizzato per incrementare il contatore.

package counter;

import javafx.fxml.FXML;

public class OuterController {

    // controller of counter.fxml injected here
    @FXML
    private CounterController countController;

    public void initialize() {
        // controller available in initialize method
        System.out.println("Current value: " + countController.getValue());
    }

    @FXML
    private void increment() {
        countController.increment();
    }

}

Gli fxmls possono essere caricati in questo modo, assumendo che il codice sia chiamato da una classe nello stesso pacchetto di outer.fxml :

Parent parent = FXMLLoader.load(getClass().getResource("outer.fxml"));

Definisci i blocchi e

A volte un elemento deve essere creato al di fuori della solita struttura di oggetti in fxml.

Qui entra in gioco Define Blocks :

I contenuti all'interno di un elemento <fx:define> non vengono aggiunti all'oggetto creato per l'elemento genitore.

Ogni elemento figlio di <fx:define> richiede un attributo fx:id .

Gli oggetti creati in questo modo possono essere successivamente referenziati usando l'elemento <fx:reference> o usando l'espressione binding.

L'elemento <fx:reference> può essere utilizzato per fare riferimento a qualsiasi elemento con un attributo fx:id che viene gestito prima che l'elemento <fx:reference> venga gestito utilizzando lo stesso valore dell'attributo fx:id dell'elemento di riferimento nel attributo source dell'elemento <fx:reference> .

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

<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>


<VBox xmlns:fx="http://javafx.com/fxml/1" prefHeight="300.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/8">
    <children>
        <fx:define>
            <String fx:value="My radio group" fx:id="text" />
        </fx:define>
        <Text>
            <text>
                <!-- reference text defined above using fx:reference -->
                <fx:reference source="text"/>
            </text>
        </Text>
        <RadioButton text="Radio 1">
            <toggleGroup>
                <ToggleGroup fx:id="group" />
            </toggleGroup>
        </RadioButton>
        <RadioButton text="Radio 2">
            <toggleGroup>
                <!-- reference ToggleGroup created for last RadioButton -->
                <fx:reference source="group"/>
            </toggleGroup>
        </RadioButton>
        <RadioButton text="Radio 3" toggleGroup="$group" />
        
        <!-- reference text defined above using expression binding -->
        <Text text="$text" />
    </children>
</VBox>

Trasmissione dei dati a FXML - accesso al controller esistente

Problema: alcuni dati devono essere passati a una scena caricata da un fxml.

Soluzione

Specificare un controller utilizzando l'attributo fx:controller e ottenere l'istanza del controller creata durante il processo di caricamento FXMLLoader utilizzata per caricare l'fxml.

Aggiungi metodi per passare i dati all'istanza del controller e gestisci i dati con questi metodi.

FXML

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

<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="valuepassing.TestController">
    <children>
        <Text fx:id="target" />
    </children>
</VBox>

controllore

package valuepassing;

import javafx.fxml.FXML;
import javafx.scene.text.Text;

public class TestController {

    @FXML
    private Text target;

    public void setData(String data) {
        target.setText(data);
    }

}

Codice utilizzato per caricare il file fxml

String data = "Hello World!";

FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml"));
Parent root = loader.load();
TestController controller = loader.<TestController>getController();
controller.setData(data);

Trasmissione dei dati a FXML - Specifica dell'istanza del controllore

Problema: alcuni dati devono essere passati a una scena caricata da un fxml.

Soluzione

Impostare il controller usando l'istanza FXMLLoader utilizzata in seguito per caricare il file fxml.

Assicurarsi che il controller contenga i dati rilevanti prima di caricare il file fxml.

Nota: in questo caso il file fxml non deve contenere l'attributo fx:controller .

FXML

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

<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns:fx="http://javafx.com/fxml/1">
    <children>
        <Text fx:id="target" />
    </children>
</VBox>

controllore

import javafx.fxml.FXML;
import javafx.scene.text.Text;

public class TestController {

    private final String data;

    public TestController(String data) {
        this.data = data;
    }
    
    @FXML
    private Text target;
    
    public void initialize() {
        // handle data once the fields are injected
        target.setText(data);
    }

}

Codice utilizzato per caricare il file fxml

String data = "Hello World!";

FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml"));

TestController controller = new TestController(data);
loader.setController(controller);

Parent root = loader.load();

Passaggio dei parametri a FXML - utilizzando un controllerFactory

Problema: alcuni dati devono essere passati a una scena caricata da un fxml.

Soluzione

Specificare una factory controller che è responsabile della creazione dei controller. Passa i dati all'istanza del controller creata dalla fabbrica.

FXML

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

<?import javafx.scene.text.*?>
<?import javafx.scene.layout.*?>

<VBox xmlns:fx="http://javafx.com/fxml/1" fx:controller="valuepassing.TestController">
    <children>
        <Text fx:id="target" />
    </children>
</VBox>

controllore

package valuepassing;

import javafx.fxml.FXML;
import javafx.scene.text.Text;

public class TestController {

    private final String data;

    public TestController(String data) {
        this.data = data;
    }
    
    @FXML
    private Text target;
    
    public void initialize() {
        // handle data once the fields are injected
        target.setText(data);
    }

}

Codice utilizzato per caricare il file fxml

String data = "Hello World!";

Map<Class, Callable<?>> creators = new HashMap<>();
creators.put(TestController.class, new Callable<TestController>() {

    @Override
    public TestController call() throws Exception {
        return new TestController(data);
    }

});

FXMLLoader loader = new FXMLLoader(getClass().getResource("test.fxml"));

loader.setControllerFactory(new Callback<Class<?>, Object>() {

    @Override
    public Object call(Class<?> param) {
        Callable<?> callable = creators.get(param);
        if (callable == null) {
            try {
                // default handling: use no-arg constructor
                return param.newInstance();
            } catch (InstantiationException | IllegalAccessException ex) {
                throw new IllegalStateException(ex);
            }
        } else {
            try {
                return callable.call();
            } catch (Exception ex) {
                throw new IllegalStateException(ex);
            }
        }
    }
});

Parent root = loader.load();

Questo può sembrare complesso, ma può essere utile, se l'fxml dovrebbe essere in grado di decidere, quale classe di controller ha bisogno.

Creazione di istanze in FXML

La seguente classe è utilizzata per dimostrare come possono essere create istanze di classi:

JavaFX 8

L'annotazione in Person(@NamedArg("name") String name) deve essere rimossa, poiché l'annotazione @NamedArg non è disponibile.

package fxml.sample;

import javafx.beans.NamedArg;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Person {

    public static final Person JOHN = new Person("John");

    public Person() {
        System.out.println("Person()");
    }
    
    public Person(@NamedArg("name") String name) {
        System.out.println("Person(String)");
        this.name.set(name);
    }
    
    public Person(Person person) {
        System.out.println("Person(Person)");
        this.name.set(person.getName());
    }

    private final StringProperty name = new SimpleStringProperty();

    public final String getName() {
        System.out.println("getter");
        return this.name.get();
    }

    public final void setName(String value) {
        System.out.println("setter");
        this.name.set(value);
    }

    public final StringProperty nameProperty() {
        System.out.println("property getter");
        return this.name;
    }
    
    public static Person valueOf(String value) {
        System.out.println("valueOf");
        return new Person(value);
    }
    
    public static Person createPerson() {
        System.out.println("createPerson");
        return new Person();
    }
    
}

Supponiamo che la classe Person sia già stata inizializzata prima di caricare il file fxml.

Una nota sulle importazioni

Nel seguente esempio fxml la sezione delle importazioni sarà esclusa. Comunque il fxml dovrebbe iniziare con

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

seguito da una sezione delle importazioni che importa tutte le classi utilizzate nel file fxml. Tali importazioni sono simili alle importazioni non statiche, ma vengono aggiunte come istruzioni di elaborazione. È necessario importare anche le classi dal pacchetto java.lang .

In questo caso è necessario aggiungere le seguenti importazioni:

<?import java.lang.*?>
<?import fxml.sample.Person?>

JavaFX 8

@NamedArg annotato @NamedArg

Se esiste un costruttore in cui ogni parametro è annotato con @NamedArg e tutti i valori delle annotazioni @NamedArg sono presenti in fxml, il costruttore verrà utilizzato con tali parametri.

<Person name="John"/>
<Person xmlns:fx="http://javafx.com/fxml">
    <name>
        <String fx:value="John"/>
    </name>
</Person>

Entrambi producono il seguente output della console, se caricato:

Person(String)

Nessun costruttore di args

Se non è disponibile un @NamedArg annotato @NamedArg adatto, @NamedArg utilizzato il costruttore che non accetta parametri.

Rimuovi l'annotazione @NamedArg dal costruttore e prova a caricare.

<Person name="John"/>

Questo userà il costruttore senza parametri.

Produzione:

Person()
setter

fx:value attributo fx:value

L'attributo fx:value può essere utilizzato per passare il suo valore a un metodo valueOf static valueOf un parametro String e restituisce l'istanza da utilizzare.

Esempio

<Person xmlns:fx="http://javafx.com/fxml" fx:value="John"/>

Produzione:

valueOf
Person(String)

fx:factory

L'attributo fx:factory consente la creazione di oggetti usando metodi static arbitrari che non prendono parametri.

Esempio

<Person xmlns:fx="http://javafx.com/fxml" fx:factory="createPerson">
    <name>
        <String fx:value="John"/>
    </name>
</Person>

Produzione:

createPerson
Person()
setter

<fx:copy>

Usando fx:copy un costruttore di copia può essere invocato. Specificare fx:id di un altro L'attributo source del tag invocherà il costruttore di copia con quell'oggetto come parametro.

Esempio:

<ArrayList xmlns:fx="http://javafx.com/fxml">
    <Person fx:id="p1" fx:constant="JOHN"/>
    <fx:copy source="p1"/>
</ArrayList>

Produzione

Person(Person)
getter

fx:constant

fx:constant consente di ottenere un valore da un campo static final .

Esempio

<Person xmlns:fx="http://javafx.com/fxml" fx:constant="JOHN"/>

non produrrà alcun output, poiché questo si riferisce solo a JOHN che è stato creato durante l'inizializzazione della classe.

Impostazione delle proprietà

Esistono diversi modi per aggiungere dati a un oggetto in fxml:

tag <property>

Un tag con il nome di una proprietà può essere aggiunto come figlio di un elemento utilizzato per creare un'istanza. Il figlio di questo tag viene assegnato alla proprietà utilizzando il setter o aggiunto al contenuto della proprietà (proprietà readonly list / map).

Proprietà di default

Una classe può essere annotata con l'annotazione @DefaultProperty . In questo caso gli elementi possono essere aggiunti direttamente come elemento figlio senza utilizzare un elemento con il nome della proprietà.

property="value" attributo property="value"

Le proprietà possono essere assegnate utilizzando il nome della proprietà come nome attributo e il valore come valore dell'attributo. Questo ha lo stesso effetto dell'aggiunta del seguente elemento come figlio del tag:

<property>
    <String fx:value="value" />
</property>

setter statici

Le proprietà possono essere impostate anche con setter static . Questi sono metodi static denominati setProperty che accettano l'elemento come primo parametro e il valore da impostare come secondo parametro. Questi metodi possono essere inseriti in qualsiasi classe e possono essere utilizzati utilizzando ContainingClass.property anziché il solito nome di proprietà.

Nota: attualmente sembra necessario disporre di un metodo getter statico corrispondente (ovvero un metodo statico denominato getProperty prende l'elemento come parametro nella stessa classe del setter statico) affinché funzioni, a meno che il tipo di valore non sia String .

Tipo coercizione

Il seguente meccanismo viene utilizzato per ottenere un oggetto della classe corretta durante le assegnazioni, ad esempio per soddisfare il tipo di parametro di un metodo setter.

Se le classi sono assegnabili, viene utilizzato il valore stesso.

Altrimenti il ​​valore viene convertito come segue

Tipo di bersaglio valore usato (valore sorgente s )
Boolean , boolean Boolean.valueOf(s)
char , Character s.toString.charAt(0)
altro tipo primitivo o tipo di wrapper metodo appropriato per il tipo di destinazione, nel caso in cui s sia un Number , il valueOf(s.toString()) per il tipo di wrapper in caso contrario
BigInteger BigInteger.valueOf(s.longValue()) is s è un Number , new BigInteger(s.toString()) altrimenti
BigDecimal BigDecimal.valueOf(s.doubleValue()) is s è un Number , new BigDecimal(s.toString()) altrimenti
Numero Double.valueOf(s.toString()) se s.toString() contiene a . , Long.valueOf(s.toString()) altrimenti
Class Class.forName(s.toString()) invocato utilizzando il contesto ClassLoader del thread corrente senza inizializzare la classe
enum Il risultato del metodo valueOf , ulteriormente convertito in una String tutta maiuscola separata da _ inserita prima di ogni lettera maiuscola, se s è una String che inizia con una lettera minuscola
altro il valore restituito da un metodo valueOf static in targetType, che ha un parametro corrispondente al tipo di s o una superclasse di quel tipo

Nota: questo comportamento non è ben documentato e potrebbe essere soggetto a modifiche.

Esempio

public enum Location {
    WASHINGTON_DC,
    LONDON;
}
package fxml.sample;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javafx.beans.DefaultProperty;

@DefaultProperty("items")
public class Sample {
    
    private Location loaction;

    public Location getLoaction() {
        return loaction;
    }

    public void setLoaction(Location loaction) {
        this.loaction = loaction;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
    
    int number;

    private final List<Object> items = new ArrayList<>();

    public List<Object> getItems() {
        return items;
    }
    
    private final Map<String, Object> map = new HashMap<>();

    public Map<String, Object> getMap() {
        return map;
    }
    
    private BigInteger serialNumber;

    public BigInteger getSerialNumber() {
        return serialNumber;
    }

    public void setSerialNumber(BigInteger serialNumber) {
        this.serialNumber = serialNumber;
    }

    @Override
    public String toString() {
        return "Sample{" + "loaction=" + loaction + ", number=" + number + ", items=" + items + ", map=" + map + ", serialNumber=" + serialNumber + '}';
    }
    
}
package fxml.sample;

public class Container {

    public static int getNumber(Sample sample) {
        return sample.number;
    }

    public static void setNumber(Sample sample, int number) {
        sample.number = number;
    }

    private final String value;

    private Container(String value) {
        this.value = value;
    }

    public static Container valueOf(String s) {
        return new Container(s);
    }

    @Override
    public String toString() {
        return "42" + value;
    }

}

Stampa il risultato del caricamento dei seguenti fxml file fxml

Sample{loaction=WASHINGTON_DC, number=5, items=[42a, 42b, 42c, 42d, 42e, 42f], map={answer=42, g=9.81, hello=42A, sample=Sample{loaction=null, number=33, items=[], map={}, serialNumber=null}}, serialNumber=4299}
<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import fxml.sample.*?>

<Sample xmlns:fx="http://javafx.com/fxml/1" Container.number="5" loaction="washingtonDc">
    
    <!-- set serialNumber property (type coercion) -->
    <serialNumber>
        <Container fx:value="99"/>
    </serialNumber>
    
    <!-- Add elements to default property-->
    <Container fx:value="a"/>
    <Container fx:value="b"/>
    <Container fx:value="c"/>
    <Container fx:value="d"/>
    <Container fx:value="e"/>
    <Container fx:value="f"/>
    
    <!-- fill readonly map property -->
    <map g="9.81">
        <hello>
            <Container fx:value="A"/>
        </hello>
        <answer>
            <Container fx:value=""/>
        </answer>
        <sample>
            <Sample>
                <!-- static setter-->
                <Container.number>
                    <Integer fx:value="33" />
                </Container.number>
            </Sample>
        </sample>
    </map>
</Sample>


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow