javafx
FXML och kontroller
Sök…
Syntax
- xmlns: fx = " http://javafx.com/fxml " // namnutrymmesdeklaration
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:
FXMLLoader
läser och analyserar FXML-filen. Det skapar objekt som motsvarar elementen som definieras i filen och noterar allafx:id
attribut som definierats på dem.Eftersom rotelementet i FXML-filen definierade ett
fx:controller
skaparFXMLLoader
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.Alla element med
fx:id
attribut definierade som har fält i regulatorn med matchande fältnamn och som antingen ärpublic
(rekommenderas inte) eller antecknas@FXML
(rekommenderas) "injiceras" i motsvarande fält. Så i det här exemplet, eftersom det finns enLabel
i FXML-filen medfx:id="label"
och ett fält i regulatorn definierat som@FXML private Label label ;
label
initialiseras medLabel
skapad avFXMLLoader
.Eventhanterare registreras med alla element i FXML-filen med
onXXX="#..."
egenskaper definierade. Dessa händelsehanterare åberopar den angivna metoden i kontrollerklassen. I detta exempel, eftersomButton
haronAction="#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.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 enURL
och enResourceBundle
. I det senare fallet kommer dessa parametrar att fyllas i avURL
representerar platsen för FXML-filen och allaResourceBundle
uppsättningar påFXMLLoader
vialoader.setResources(...)
. Endera av dessa kan varanull
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:
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?>
@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>