javafx
FXML en controllers
Zoeken…
Syntaxis
- xmlns: fx = " http://javafx.com/fxml " // naamruimteaangifte
Voorbeeld FXML
Een eenvoudig FXML-document met een AnchorPane
met een knop en een AnchorPane
:
<?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>
Dit voorbeeld FXML-bestand is gekoppeld aan een controllerklasse. De associatie tussen de FXML en de controllerklasse wordt in dit geval gemaakt door de klassenaam op te geven als de waarde van het kenmerk fx:controller
in het root-element van de FXML: fx:controller="com.example.FXMLDocumentController"
. Met de controllerklasse kan Java-code worden uitgevoerd als reactie op gebruikersacties op de UI-elementen die zijn gedefinieerd in het FXML-bestand:
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
}
}
Een FXMLLoader
kan worden gebruikt om het FXML-bestand te laden:
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();
}
}
De load
methode voert verschillende acties, en is het nuttig om de volgorde waarin ze zich voordoen te begrijpen. In dit eenvoudige voorbeeld:
De
FXMLLoader
leest en parseert het FXML-bestand. Het maakt objecten die overeenkomen met de elementen die in het bestand zijn gedefinieerd, en noteert eventuelefx:id
attributen die erop zijn gedefinieerd.Aangezien het root-element van het FXML-bestand een kenmerk
fx:controller
gedefinieerd, maakt deFXMLLoader
een nieuwe instantie van de klasse die hij opgeeft. Standaard gebeurt dit door de constructor zonder argument voor de opgegeven klasse aan te roepen.Alle elementen met gedefinieerde
fx:id
kenmerken die velden in de controller hebben met overeenkomende veldnamen, en die ofwelpublic
(niet aanbevolen) of geannoteerd@FXML
(aanbevolen) zijn, worden "geïnjecteerd" in die overeenkomstige velden. In dit voorbeeld is er dus eenLabel
in het FXML-bestand metfx:id="label"
en een veld in de controller gedefinieerd als@FXML private Label label ;
het
label
veld wordt geïnitialiseerd met deLabel
bijvoorbeeld door deFXMLLoader
.Gebeurtenishandlers worden geregistreerd bij alle elementen in het FXML-bestand met de
onXXX="#..."
gedefinieerd. Deze gebeurtenishandlers roepen de opgegeven methode in de controllerklasse aan. In dit voorbeeld heeft deButton
onAction="#handleButtonAction"
en definieert de controller een methode@FXML private void handleButtonAction(ActionEvent event) { ... }
wanneer een actie op de knop wordt geactiveerd (bijvoorbeeld wanneer de gebruiker erop drukt), wordt deze methode aangeroepen. De methode moet een
void
ActionEvent
hebben en kan een parameter definiëren die overeenkomt met het gebeurtenistype (ActionEvent
in dit voorbeeld) of kan geen parameters definiëren.Als ten slotte de controllerklasse een
initialize
definieert, wordt deze methode aangeroepen. Merk op dat dit gebeurt nadat de@FXML
velden zijn geïnjecteerd, zodat ze veilig toegankelijk zijn via deze methode en worden geïnitialiseerd met de instanties die overeenkomen met de elementen in het FXML-bestand. De methodeinitialize()
kan geen parameters aannemen, of eenURL
en eenResourceBundle
. In het laatste geval worden deze parameters ingevuld door deURL
die de locatie van het FXML-bestand vertegenwoordigt, en elkeResourceBundle
die op deFXMLLoader
vialoader.setResources(...)
. Beide kunnennull
als ze niet waren ingesteld.
Geneste controllers
Het is niet nodig om de hele gebruikersinterface in een enkele FXML te maken met behulp van een enkele controller.
De tag <fx:include>
kan worden gebruikt om het ene fxml-bestand in het andere op te nemen. De controller van de meegeleverde fxml kan net als elk ander object dat door de FXMLLoader
gemaakt, in de controller van het bijbehorende bestand worden geïnjecteerd.
Dit wordt gedaan door het kenmerk fx:id
toe te voegen aan het element <fx:include>
. Op deze manier wordt de controller van de meegeleverde fxml in het veld geïnjecteerd met de naam <fx:id value>Controller
.
Voorbeelden:
fx: id waarde | veldnaam voor injectie |
---|---|
foo | fooController |
answer42 | answer42Controller |
xyz | xYzController |
Voorbeeld fxmls
Teller
Dit is een fxml met een StackPane
met een Text
knooppunt. Met de controller voor dit fxml-bestand kan de huidige tellerwaarde worden opgehaald en de teller worden verhoogd:
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;
}
}
Inclusief 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
De controller van de meegeleverde fxml wordt in deze controller geïnjecteerd. Hier wordt de handler voor de gebeurtenis onAction
voor de Button
gebruikt om de teller te verhogen.
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();
}
}
De fxmls kunnen op deze manier worden geladen, ervan uitgaande dat de code wordt aangeroepen vanuit een klasse in hetzelfde pakket als outer.fxml
:
Parent parent = FXMLLoader.load(getClass().getResource("outer.fxml"));
Definieer blokken en
Soms moet een element buiten de gebruikelijke objectstructuur in de fxml worden gemaakt.
Dit is waar Define Blocks in het spel komen:
Inhoud in een element <fx:define>
wordt niet toegevoegd aan het object dat voor het bovenliggende element is gemaakt.
Elk onderliggend element van het <fx:define>
heeft een fx:id
attribuut nodig.
Naar objecten die op deze manier zijn gemaakt, kan later worden verwezen met het element <fx:reference>
of met expressiebinding.
Het element <fx:reference>
kan worden gebruikt om te verwijzen naar elk element met een kenmerk fx:id
dat wordt verwerkt voordat het element <fx:reference>
wordt afgehandeld met dezelfde waarde als het kenmerk fx:id
van het element waarnaar wordt verwezen in de source
attribuut van de <fx:reference>
element.
<?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>
Gegevens doorgeven aan FXML - toegang tot bestaande controller
Probleem: sommige gegevens moeten worden doorgegeven aan een scène die is geladen vanuit een fxml.
Oplossing
Geef een controller op met behulp van het kenmerk fx:controller
en haal de controllerinstantie die tijdens het laadproces is gemaakt van de FXMLLoader
instantie die wordt gebruikt om de fxml te laden.
Voeg methoden toe om de gegevens door te geven aan de controller-instantie en behandel de gegevens in die methoden.
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>
controleur
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);
}
}
Code die wordt gebruikt voor het laden van de 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);
Gegevens doorgeven aan FXML - Het controller-exemplaar opgeven
Probleem: sommige gegevens moeten worden doorgegeven aan een scène die is geladen vanuit een fxml.
Oplossing
Stel de controller in met behulp van de FXMLLoader
instantie die later wordt gebruikt om de fxml te laden.
Zorg ervoor dat de controller de relevante gegevens bevat voordat u de fxml laadt.
Opmerking: in dit geval mag het fxml-bestand niet het kenmerk fx:controller
bevatten.
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>
controleur
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);
}
}
Code die wordt gebruikt voor het laden van de 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();
Parameters doorgeven aan FXML - met behulp van een controllerFactory
Probleem: sommige gegevens moeten worden doorgegeven aan een scène die is geladen vanuit een fxml.
Oplossing
Geef een controllerfabriek op die verantwoordelijk is voor het maken van de controllers. Geef de gegevens door aan de controllerinstantie die door de fabriek is gemaakt.
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>
controleur
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);
}
}
Code die wordt gebruikt voor het laden van de fxml
String data = "Hallo wereld!";
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();
Dit lijkt misschien ingewikkeld, maar het kan handig zijn als de fxml moet kunnen beslissen welke controller-klasse het nodig heeft.
Instance van exemplaren in FXML
De volgende klasse wordt gebruikt om aan te tonen hoe instanties van klassen kunnen worden gemaakt:
De annotatie in Person(@NamedArg("name") String name)
moet worden verwijderd, omdat de annotatie @NamedArg
niet beschikbaar is.
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();
}
}
Stel dat de klasse Person
al is geïnitialiseerd voordat de fxml wordt geladen.
Een opmerking over invoer
In het volgende fxml-voorbeeld wordt de importsectie weggelaten. De fxml zou echter moeten beginnen met
<?xml version="1.0" encoding="UTF-8"?>
gevolgd door een importsectie die alle klassen importeert die in het fxml-bestand worden gebruikt. Die invoer is vergelijkbaar met niet-statische invoer, maar wordt toegevoegd als verwerkingsinstructies. Zelfs klassen uit het pakket java.lang
moeten worden geïmporteerd.
In dit geval moeten de volgende invoer worden toegevoegd:
<?import java.lang.*?>
<?import fxml.sample.Person?>
@NamedArg
geannoteerde constructor
Als er een constructor is waarbij elke parameter is geannoteerd met @NamedArg
en alle waarden van de @NamedArg
annotaties aanwezig zijn in de fxml, wordt de constructor met die parameters gebruikt.
<Person name="John"/>
<Person xmlns:fx="http://javafx.com/fxml">
<name>
<String fx:value="John"/>
</name>
</Person>
Beide resulteren in de volgende console-uitvoer, indien geladen:
Person(String)
Geen args constructor
Als er geen geschikte geannoteerde @NamedArg
constructor beschikbaar is, wordt de constructor gebruikt die geen parameters gebruikt.
Verwijder de annotatie @NamedArg
van de constructor en probeer te laden.
<Person name="John"/>
Dit gebruikt de constructor zonder parameters.
Output:
Person()
setter
fx:value
Het kenmerk fx:value
kan worden gebruikt om de waarde door te geven aan een static
valueOf
methode die een parameter String
en de te gebruiken instantie teruggeeft.
Voorbeeld
<Person xmlns:fx="http://javafx.com/fxml" fx:value="John"/>
Output:
valueOf
Person(String)
fx:factory
Met het kenmerk fx:factory
kunnen objecten worden gemaakt met willekeurige static
methoden die geen parameters aannemen.
Voorbeeld
<Person xmlns:fx="http://javafx.com/fxml" fx:factory="createPerson">
<name>
<String fx:value="John"/>
</name>
</Person>
Output:
createPerson
Person()
setter
<fx:copy>
Met behulp van fx:copy
een copy constructor worden aangeroepen. Onder vermelding van de fx:id
van een andere De source
attribuut van de tag wordt de kopie constructeur met dat object als parameter te roepen.
Voorbeeld:
<ArrayList xmlns:fx="http://javafx.com/fxml">
<Person fx:id="p1" fx:constant="JOHN"/>
<fx:copy source="p1"/>
</ArrayList>
uitgang
Person(Person)
getter
fx:constant
fx:constant
maakt het mogelijk een waarde te krijgen uit een static final
veld.
Voorbeeld
<Person xmlns:fx="http://javafx.com/fxml" fx:constant="JOHN"/>
zal geen uitvoer produceren, omdat dit alleen verwijst naar JOHN
die is gemaakt bij het initialiseren van de klasse.
Eigenschappen instellen
Er zijn meerdere manieren om gegevens toe te voegen aan een object in fxml:
<property>
tag
Een tag met de naam van een eigenschap kan worden toegevoegd als onderliggend element van een element dat wordt gebruikt voor het maken van een instantie. Het kind van deze tag wordt aan de eigenschap toegewezen met behulp van de setter of toegevoegd aan de inhoud van de eigenschap (alleen-lezen lijst / kaarteigenschappen).
Standaard eigenschap
Een klasse kan worden geannoteerd met de annotatie @DefaultProperty
. In dit geval kunnen elementen rechtstreeks worden toegevoegd als onderliggend element zonder een element met de naam van de eigenschap te gebruiken.
property="value"
attribuut
Eigenschappen kunnen worden toegewezen met behulp van de eigenschapsnaam als kenmerknaam en de waarde als kenmerkwaarde. Dit heeft hetzelfde effect als het toevoegen van het volgende element als onderliggend element van de tag:
<property>
<String fx:value="value" />
</property>
statische setters
Eigenschappen kunnen ook worden ingesteld met behulp van static
setters. Dit zijn static
methoden met de naam setProperty
die het element als eerste parameter nemen en de waarde die als tweede parameter moet worden ingesteld. Die methoden kunnen zich in elke klasse bevinden en kunnen worden gebruikt met ContainingClass.property
plaats van de gebruikelijke eigenschapsnaam.
Opmerking: Momenteel lijkt het nodig om een overeenkomstige statische getter-methode te hebben (dwz een statische methode genaamd getProperty
die het element als parameter in dezelfde klasse als de statische setter neemt) om dit te laten werken, tenzij het waardetype String
.
Type dwang
Het volgende mechanisme wordt gebruikt om een object van de juiste klasse te krijgen tijdens toewijzingen, bijvoorbeeld voor het parametertype van een setter-methode.
Als de klassen toewijsbaar zijn, wordt de waarde zelf gebruikt.
Anders wordt de waarde als volgt geconverteerd
Type doel | gebruikte waarde (bronwaarde s ) |
---|---|
Boolean , boolean | Boolean.valueOf(s) |
char , Character | s.toString.charAt(0) |
ander primitief type of wikkeltype | geschikte methode voor het valueOf(s.toString()) , in het geval dat de s een Number , anders de waarde van valueOf(s.toString()) voor het wrapper-type |
BigInteger | BigInteger.valueOf(s.longValue()) is s is een Number , new BigInteger(s.toString()) anders |
BigDecimal | BigDecimal.valueOf(s.doubleValue()) is s is een Number , new BigDecimal(s.toString()) anders |
Aantal | Double.valueOf(s.toString()) als s.toString() bevat . , Long.valueOf(s.toString()) anders |
Class | Class.forName(s.toString()) aangeroepen met de context ClassLoader van de huidige thread zonder de klasse te initialiseren |
enum | Het resultaat van de methode valueOf , bovendien geconverteerd naar een String hoofdletters gescheiden door _ ingevoegd voor elke hoofdletter, als s een String die begint met een kleine letter |
anders | de waarde die wordt geretourneerd door een static valueOf methode in het targetType, die een parameter heeft die overeenkomt met het type s of een superklasse van dat type |
Opmerking: dit gedrag is niet goed gedocumenteerd en kan aan verandering onderhevig zijn.
Voorbeeld
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;
}
}
Het resultaat afdrukken van het laden van de onderstaande fxml
bestandsopbrengsten
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>