Sök…


Syntax

Exempel FXML

Ett enkelt FXML-dokument som beskriver ett AnchorPane innehåller en knapp och en etikettnod:

<?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>

Detta exempel FXML-fil är associerat med en kontrollerklass. Föreningen mellan FXML och controller-klassen, i detta fall, skapas genom att ange klassnamnet som värdet på fx:controller attributet i rotelementet i FXML: fx:controller="com.example.FXMLDocumentController" . Controllerklassen tillåter att Java-kod kan köras som svar på användaråtgärder på UI-elementen som definieras i FXML-filen:

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
    }    
    
}

En FXMLLoader kan användas för att ladda FXML-filen:

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();
    }

}

load utför flera åtgärder, och det är användbart att förstå i vilken ordning de sker. I detta enkla exempel:

  1. FXMLLoader läser och analyserar FXML-filen. Det skapar objekt som motsvarar elementen som definieras i filen och noterar alla fx:id attribut som definierats på dem.

  2. Eftersom rotelementet i FXML-filen definierade ett fx:controller skapar FXMLLoader en ny instans av klassen som den anger. Som standard händer detta genom att åberopa konstruktören utan argument för den angivna klassen.

  3. Alla element med fx:id attribut definierade som har fält i regulatorn med matchande fältnamn och som antingen är public (rekommenderas inte) eller antecknas @FXML (rekommenderas) "injiceras" i motsvarande fält. Så i det här exemplet, eftersom det finns en Label i FXML-filen med fx:id="label" och ett fält i regulatorn definierat som

    @FXML
    private Label label ;
    

    label initialiseras med Label skapad av FXMLLoader .

  4. Eventhanterare registreras med alla element i FXML-filen med onXXX="#..." egenskaper definierade. Dessa händelsehanterare åberopar den angivna metoden i kontrollerklassen. I detta exempel, eftersom Button har onAction="#handleButtonAction" , och regulatorn definierar en metod

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

    när en åtgärd avfyras på knappen (t.ex. användaren trycker på den) påkallas denna metod. Metoden måste ha en void returtyp och kan antingen definiera en parameter som matchar händelsetypen ( ActionEvent i detta exempel) eller kan inte definiera några parametrar.

  5. Slutligen, om kontrollerklassen definierar en initialize , anropas denna metod. Observera att detta händer efter att @FXML fälten har injicerats, så att de kan komma åt på ett säkert sätt med den här metoden och kommer att initialiseras med de instanser som motsvarar elementen i FXML-filen. initialize() kan antingen inte ta några parametrar eller ta en URL och en ResourceBundle . I det senare fallet kommer dessa parametrar att fyllas i av URL representerar platsen för FXML-filen och alla ResourceBundle uppsättningar på FXMLLoader via loader.setResources(...) . Endera av dessa kan vara null om de inte var inställda.

Kapslade kontroller

Det finns inget behov att skapa hela användargränssnittet i en enda FXML med en enda kontroller.

Taggen <fx:include> kan användas för att inkludera en fxml-fil i en annan. Styrenheten för den medföljande fxml kan injiceras i styrenheten för den inkluderande filen precis som alla andra objekt skapade av FXMLLoader .

Detta görs genom att lägga till fx:id attributet till elementet <fx:include> . På så sätt injiceras styrenheten för den inkluderade fxml till fältet med namnet <fx:id value>Controller .

Exempel:

fx: id-värde fältnamn för injektion
foo fooController
answer42 answer42Controller
XYZ xYzController

Prov fxmls

Disken

Detta är en fxml innehållande en StackPane med en Text nod. Styrenheten för denna fxml-fil gör det möjligt att få det aktuella räknarvärdet samt öka räknaren:

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;
    }
    
}

Inklusive 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

Styrenheten för den medföljande fxml injiceras till den här kontrollenheten. Här används hanteraren för onAction händelsen för Button för att öka räknaren.

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();
    }

}

Fxmls kan laddas så här, förutsatt att koden kallas från en klass i samma paket som outer.fxml :

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

Definiera block och

Ibland måste ett element skapas utanför den vanliga objektstrukturen i fxml.

Det är här Define Blocks spelar in:

Innehåll i ett <fx:define> -element läggs inte till objektet som skapats för överordnade element.

Varje barnelement i <fx:define> behöver ett fx:id attribut.

Objekt som skapats på detta sätt kan senare refereras med hjälp av elementet <fx:reference> eller med hjälp av uttrycksbindning.

Elementet <fx:reference> kan användas för att referera till alla element med ett fx:id attribut som hanteras innan <fx:reference> -elementet hanteras genom att använda samma värde som fx:id attributet för det refererade elementet i source attributet för <fx:reference> elementet.

<?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>

Vidarebefordra data till FXML - åtkomst till befintlig controller

Problem: Vissa data måste skickas till en scen laddad från en fxml.

Lösning

Ange en controller med fx:controller attributet och få kontrollenhetens instans skapad under laddningsprocessen från FXMLLoader instansen som används för att ladda fxml.

Lägg till metoder för att skicka data till controllerinstansen och hantera data på dessa metoder.

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>

Kontrollant

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);
    }

}

Kod som används för att ladda 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);

Vidarebefordra data till FXML - Ange controller-instansen

Problem: Vissa data måste skickas till en scen laddad från en fxml.

Lösning

Ställ in regulatorn med FXMLLoader instansen som används senare för att ladda fxml.

Se till att regulatorn innehåller relevant information innan du laddar fxml.

Obs: i detta fall får fxml-filen inte innehålla fx:controller attributet.

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>

Kontrollant

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);
    }

}

Kod som används för att ladda 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();

Vidarebefordra parametrar till FXML - med hjälp av en controllerFactory

Problem: Vissa data måste skickas till en scen laddad från en fxml.

Lösning

Ange en kontrollfabrik som ansvarar för att skapa kontrollerna. Vidarebefordra uppgifterna till den controllerinstans som skapats av fabriken.

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>

Kontrollant

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);
    }

}

Kod som används för att ladda fxml

String data = "Hej världen!";

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();

Detta kan verka komplicerat, men det kan vara användbart om fxml ska kunna bestämma vilken kontrollerklass den behöver.

Instansskapande i FXML

Följande klass används för att visa hur instanser av klasser kan skapas:

JavaFX 8

Annotationen i Person(@NamedArg("name") String name) måste tas bort, eftersom @NamedArg kommentaren inte är tillgänglig.

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();
    }
    
}

Antag att Person redan har initierats innan fxml laddas.

En anteckning om import

I följande fxml-exempel kommer importavsnittet att utelämnas. Men fxml bör börja med

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

följt av ett importavsnitt som importerar alla klasser som används i fxml-filen. Denna import liknar icke-statisk import, men läggs till som behandlingsinstruktioner. Även klasser från paketet java.lang måste importeras.

I detta fall ska följande import läggas till:

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

JavaFX 8

@NamedArg kommenterade konstruktör

Om det finns en konstruktör där varje parameter är antecknad med @NamedArg och alla värden på @NamedArg kommentarerna finns i fxml, kommer konstruktorn att användas med dessa parametrar.

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

Båda resulterar i följande konsolutgång, om de laddas:

Person(String)

Ingen args-konstruktör

Om det inte finns någon lämplig @NamedArg kommenterad konstruktör finns den konstruktören som inte tar några parametrar kommer att användas.

Ta bort @NamedArg anteckningen från konstruktören och försök ladda.

<Person name="John"/>

Detta kommer att använda konstruktören utan parametrar.

Produktion:

Person()
setter

fx:value attribut

Attributet fx:value kan användas för att överföra det till ett static valueOf metod som tar en String och returnerar instansen att använda.

Exempel

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

Produktion:

valueOf
Person(String)

fx:factory

fx:factory gör det möjligt att skapa objekt med godtyckliga static metoder som inte tar parametrar.

Exempel

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

Produktion:

createPerson
Person()
setter

<fx:copy>

Använda fx:copy en kopieringskonstruktör kan åberopas. Specificera fx:id av en annan Den source attribut av etiketten kommer att åberopa kopian konstruktören med det objektet som parameter.

Exempel:

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

Produktion

Person(Person)
getter

fx:constant

fx:constant tillåter att få ett värde från ett static final slutfält.

Exempel

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

kommer inte att producera någon utgång, eftersom det bara hänvisar till JOHN som skapades vid initialisering av klassen.

Ställa in egenskaper

Det finns flera sätt att lägga till data till ett objekt i fxml:

<property> -tagg

En tagg med namnet på en egenskap kan läggas till som barn till ett element som används för att skapa en instans. Barnet till denna tagg tilldelas egenskapen med hjälp av setteren eller läggs till i innehållet i egenskapen (läslista / kartegenskaper).

Standardegenskap

En klass kan kommenteras med @DefaultProperty anteckningen. I detta fall kan element direkt läggas till som underordnad element utan att använda ett element med egenskapens namn.

property="value" attribut

Egenskaper kan tilldelas med egenskapens namn som attributnamn och värdet som attributvärde. Detta har samma effekt som att lägga till följande element som underordnat taggen:

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

statiska bosättare

Egenskaper kan också ställas in med static inställningar. Dessa är static metoder som heter setProperty som tar elementet som första parameter och värdet som ska ställas in som andra parameter. Dessa metoder kan återkomma i valfri klass och kan användas med ContainingClass.property istället för det vanliga egendomsnamnet.

Obs: För närvarande verkar det vara nödvändigt att ha en motsvarande statisk getter-metod (dvs. en statisk metod med namnet getProperty tar elementet som parameter i samma klass som statisk setter) för att detta ska fungera såvida inte värdetypen är String .

Skriv tvång

Följande mekanism används för att få ett objekt i rätt klass under uppdrag, t.ex. för att passa parametertypen för en setter-metod.

Om klasserna kan tilldelas, används själva värdet.

I annat fall konverteras värdet enligt följande

Måltyp använt värde (källvärde s )
Boolean , boolean Boolean.valueOf(s)
char , Character s.toString.charAt(0)
annan primitiv typ eller omslagstyp lämplig metod för måltyp, om s är ett Number , valueOf(s.toString()) för omslagstypen annars
BigInteger BigInteger.valueOf(s.longValue()) är s är ett Number , new BigInteger(s.toString()) annars
BigDecimal BigDecimal.valueOf(s.doubleValue()) är s är ett Number , new BigDecimal(s.toString()) annars
siffra Double.valueOf(s.toString()) om s.toString() innehåller en . , Long.valueOf(s.toString()) annars
Class Class.forName(s.toString()) åberopas med hjälp av kontext ClassLoader för den aktuella tråden utan att initialisera klassen
enum Resultatet av metoden valueOf konverteras dessutom till en alla versaler String separerad av _ infogad före varje stor bokstav, om s är en String som börjar med en liten bokstav
Övrig värdet som returneras med en static valueOf metod i targetType, som har en parameter som matchar typen av s eller en superklass av den typen

Obs: Detta beteende är inte väl dokumenterat och kan komma att ändras.

Exempel

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;
    }

}

Skriva ut resultatet av att ladda nedanstående fxml fil

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow