javafx
FXML i kontrolery
Szukaj…
Składnia
- xmlns: fx = " http://javafx.com/fxml " // deklaracja przestrzeni nazw
Przykład FXML
Prosty dokument FXML opisujący AnchorPane
zawierający przycisk i węzeł etykiety:
<?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>
Ten przykładowy plik FXML jest powiązany z klasą kontrolera. Powiązanie między FXML a klasą kontrolera w tym przypadku polega na określeniu nazwy klasy jako wartości atrybutu fx:controller
w elemencie głównym FXML: fx:controller="com.example.FXMLDocumentController"
. Klasa kontrolera pozwala na wykonanie kodu Java w odpowiedzi na działania użytkownika dotyczące elementów interfejsu użytkownika zdefiniowanych w pliku 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
}
}
Do załadowania pliku FXMLLoader
można użyć programu FXMLLoader:
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();
}
}
Metoda load
wykonuje kilka akcji i warto zrozumieć kolejność ich występowania. W tym prostym przykładzie:
FXMLLoader
odczytuje i analizuje plik FXML. Tworzy obiekty odpowiadające elementom zdefiniowanym w pliku i odnotowuje zdefiniowane na nich atrybutyfx:id
.Ponieważ element główny pliku FXML zdefiniował atrybut
fx:controller
,FXMLLoader
tworzy nową instancję określonej przez siebie klasy. Domyślnie dzieje się tak przez wywołanie konstruktora bez argumentu dla określonej klasy.Dowolne elementy ze zdefiniowanymi atrybutami
fx:id
które mają pola w kontrolerze z pasującymi nazwami pól i które są albopublic
(niezalecane), albo opatrzone adnotacjami@FXML
(zalecane) są „wstrzykiwane” do odpowiednich pól. W tym przykładzie, ponieważ w pliku FXML znajduje sięLabel
ofx:id="label"
i pole w kontrolerze zdefiniowane jako@FXML private Label label ;
pole
label
jest inicjowane instancjąLabel
utworzoną przezFXMLLoader
.Procedury obsługi zdarzeń są rejestrowane z dowolnymi elementami w pliku
onXXX="#..."
ze zdefiniowanymi właściwościamionXXX="#..."
. Te procedury obsługi zdarzeń wywołują określoną metodę w klasie kontrolera. W tym przykładzie, ponieważButton
maonAction="#handleButtonAction"
, a kontroler definiuje metodę@FXML private void handleButtonAction(ActionEvent event) { ... }
gdy akcja jest uruchamiana na przycisku (np. użytkownik go naciska), ta metoda jest wywoływana. Metoda musi mieć
void
typ zwracany i może albo zdefiniować parametr pasujący do typu zdarzenia (ActionEvent
w tym przykładzie), albo nie może zdefiniować żadnych parametrów.Na koniec, jeśli klasa kontrolera definiuje metodę
initialize
metoda ta jest wywoływana. Zauważ, że dzieje się to po@FXML
pól@FXML
, aby można było bezpiecznie uzyskać do nich dostęp za pomocą tej metody i będą inicjowane instancjami odpowiadającymi elementom w pliku FXML. Metodainitialize()
może nie przyjmować parametrów lub może przyjmowaćURL
iResourceBundle
. W tym drugim przypadku parametry te zostaną wypełnioneURL
reprezentującym lokalizację pliku FXML oraz dowolnym zestawemResourceBundle
ustawionym naFXMLLoader
za pośrednictwemloader.setResources(...)
. Każda z nich może byćnull
jeśli nie została ustawiona.
Zagnieżdżone kontrolery
Nie ma potrzeby tworzenia całego interfejsu użytkownika w jednym FXML za pomocą jednego kontrolera.
Znacznika <fx:include>
można użyć do włączenia jednego pliku fxml do drugiego. Kontroler dołączonego pliku fxml można wstrzyknąć do kontrolera dołączonego pliku, tak jak każdy inny obiekt utworzony przez FXMLLoader
.
Odbywa się to poprzez dodanie atrybutu fx:id
do elementu <fx:include>
. W ten sposób kontroler dołączonego pliku fxml zostanie wstrzyknięty do pola o nazwie <fx:id value>Controller
.
Przykłady:
fx: wartość id | nazwa pola do wstrzyknięcia |
---|---|
bla | fooController |
odpowiedź42 | answer42Controller |
xYz | xYzController |
Przykładowe fxmls
Licznik
To jest plik fxml zawierający StackPane
z węzłem Text
. Kontroler dla tego pliku fxml umożliwia uzyskanie bieżącej wartości licznika, a także zwiększenie licznika:
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;
}
}
W tym 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
Kontroler dołączonego pliku fxml jest wstrzykiwany do tego kontrolera. Tutaj onAction
obsługi zdarzenia onAction
dla Button
służy do zwiększania licznika.
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();
}
}
Pliki fxml mogą być ładowane w ten sposób, zakładając, że kod jest wywoływany z klasy w tym samym pakiecie co outer.fxml
:
Parent parent = FXMLLoader.load(getClass().getResource("outer.fxml"));
Zdefiniuj bloki i
Czasami element musi zostać utworzony poza zwykłą strukturą obiektową w fxml.
Oto miejsce, w które wchodzą Define Blocks :
Zawartość wewnątrz elementu <fx:define>
nie jest dodawana do obiektu utworzonego dla elementu nadrzędnego.
Każdy element podrzędny <fx:define>
potrzebuje atrybutu fx:id
.
Do obiektów utworzonych w ten sposób można później odwoływać się za pomocą elementu <fx:reference>
lub przez powiązanie wyrażenia.
Element <fx:reference>
może służyć do odwoływania się do dowolnego elementu z atrybutem fx:id
który jest obsługiwany przed obsługą elementu <fx:reference>
przy użyciu tej samej wartości, co atrybut fx:id
przywoływanego elementu w elemencie atrybut source
elementu <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>
Przekazywanie danych do FXML - dostęp do istniejącego kontrolera
Problem: Niektóre dane muszą zostać przekazane do sceny załadowanej z pliku fxml.
Rozwiązanie
Podaj kontroler za pomocą atrybutu fx:controller
i pobierz instancję kontrolera utworzoną podczas procesu ładowania z instancji FXMLLoader
użytej do załadowania pliku fxml.
Dodaj metody przekazywania danych do instancji kontrolera i przetwarzaj dane w tych metodach.
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>
Kontroler
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 używany do ładowania pliku 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);
Przekazywanie danych do FXML - Określanie instancji kontrolera
Problem: Niektóre dane muszą zostać przekazane do sceny załadowanej z pliku fxml.
Rozwiązanie
Ustaw kontroler za pomocą instancji FXMLLoader
użytej później do załadowania pliku fxml.
Upewnij się, że kontroler zawiera odpowiednie dane przed załadowaniem pliku fxml.
Uwaga: w tym przypadku plik fxml nie może zawierać atrybutu 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>
Kontroler
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 używany do ładowania pliku 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();
Przekazywanie parametrów do FXML - za pomocą kontroleraFactory
Problem: Niektóre dane muszą zostać przekazane do sceny załadowanej z pliku fxml.
Rozwiązanie
Określ fabrykę kontrolerów odpowiedzialną za tworzenie kontrolerów. Przekaż dane do instancji kontrolera utworzonej przez fabrykę.
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>
Kontroler
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 używany do ładowania pliku fxml
Dane ciągu = „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();
Może się to wydawać skomplikowane, ale może być przydatne, jeśli plik fxml powinien być w stanie zdecydować, jakiej klasy kontrolera potrzebuje.
Tworzenie instancji w FXML
Poniższa klasa służy do zademonstrowania sposobu tworzenia instancji klas:
Adnotacja w Person(@NamedArg("name") String name)
musi zostać usunięta, ponieważ adnotacja @NamedArg
jest niedostępna.
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();
}
}
Załóżmy, że klasa Person
została już zainicjowana przed załadowaniem pliku fxml.
Uwaga na temat importu
W poniższym przykładzie fxml sekcja importowania zostanie pominięta. Jednak fxml powinien zaczynać się od
<?xml version="1.0" encoding="UTF-8"?>
następnie sekcja importu importująca wszystkie klasy użyte w pliku fxml. Import ten jest podobny do importu niestatycznego, ale jest dodawany jako instrukcja przetwarzania. Nawet klasy z pakietu java.lang
muszą zostać zaimportowane.
W takim przypadku należy dodać następujący import:
<?import java.lang.*?>
<?import fxml.sample.Person?>
@NamedArg
konstruktor z adnotacjami
Jeśli istnieje konstruktor, w którym każdy parametr jest opatrzony adnotacją @NamedArg
a wszystkie wartości adnotacji @NamedArg
są obecne w pliku fxml, konstruktor zostanie użyty z tymi parametrami.
<Person name="John"/>
<Person xmlns:fx="http://javafx.com/fxml">
<name>
<String fx:value="John"/>
</name>
</Person>
Oba dają następujące dane wyjściowe konsoli, jeśli są załadowane:
Person(String)
Brak konstruktora argumentów
Jeśli nie ma odpowiedniego konstruktora z adnotacjami @NamedArg
dostępny będzie konstruktor, który nie przyjmuje parametrów.
Usuń adnotację @NamedArg
z konstruktora i spróbuj załadować.
<Person name="John"/>
Spowoduje to użycie konstruktora bez parametrów.
Wynik:
Person()
setter
fx:value
atrybut fx:value
Atrybutu fx:value
można użyć do przekazania jego wartości do metody static
valueOf
, biorąc parametr String
i zwracając instancję do użycia.
Przykład
<Person xmlns:fx="http://javafx.com/fxml" fx:value="John"/>
Wynik:
valueOf
Person(String)
fx:factory
Atrybut fx:factory
umożliwia tworzenie obiektów przy użyciu dowolnych static
metod, które nie przyjmują parametrów.
Przykład
<Person xmlns:fx="http://javafx.com/fxml" fx:factory="createPerson">
<name>
<String fx:value="John"/>
</name>
</Person>
Wynik:
createPerson
Person()
setter
<fx:copy>
Za pomocą fx:copy
można wywołać konstruktora kopiowania. Określanie fx:id
innego Atrybut source
znacznika wywoła konstruktor kopiowania z tym obiektem jako parametrem.
Przykład:
<ArrayList xmlns:fx="http://javafx.com/fxml">
<Person fx:id="p1" fx:constant="JOHN"/>
<fx:copy source="p1"/>
</ArrayList>
Wynik
Person(Person)
getter
fx:constant
fx:constant
pozwala uzyskać wartość ze static final
pola static final
.
Przykład
<Person xmlns:fx="http://javafx.com/fxml" fx:constant="JOHN"/>
nie wygeneruje żadnych danych wyjściowych, ponieważ odnosi się to tylko do JOHN
który został utworzony podczas inicjowania klasy.
Ustawianie właściwości
Istnieje wiele sposobów dodawania danych do obiektu w formacie fxml:
Znacznik <property>
Znacznik z nazwą właściwości można dodać jako element podrzędny elementu używanego do tworzenia instancji. Element podrzędny tego znacznika jest przypisywany do właściwości za pomocą ustawiacza lub dodawany do zawartości właściwości (tylko do odczytu właściwości listy / mapy).
Właściwość domyślna
Klasę można @DefaultProperty
adnotacją za @DefaultProperty
adnotacji @DefaultProperty
. W takim przypadku elementy można dodawać bezpośrednio jako element podrzędny bez użycia elementu o nazwie właściwości.
property="value"
atrybut property="value"
Właściwości można przypisać, używając nazwy właściwości jako nazwy atrybutu i wartości jako wartości atrybutu. Ma to taki sam efekt, jak dodanie następującego elementu jako elementu podrzędnego znacznika:
<property>
<String fx:value="value" />
</property>
setery statyczne
Właściwości można ustawić również za pomocą ustawień static
. Są to metody static
nazwie setProperty
które przyjmują element jako pierwszy parametr, a wartość ustawiają jako drugi parametr. Metody te można uzyskać w dowolnej klasie i można ich używać przy użyciu ContainingClass.property
zamiast zwykłej nazwy właściwości.
Uwaga: obecnie wydaje się, że konieczne jest posiadanie odpowiedniej metody statycznego gettera (tj. Metody statycznej o nazwie getProperty
przyjmującej element jako parametr w tej samej klasie, co seter statyczny), aby działało, chyba że typem wartości jest String
.
Wpisz Coercion
Poniższy mechanizm służy do uzyskania obiektu poprawnej klasy podczas przypisań, np. W celu dopasowania typu parametru metody ustawiającej.
Jeśli klasy można przypisać, wówczas używana jest sama wartość.
W przeciwnym razie wartość jest konwertowana w następujący sposób
Typ docelowy | Wartość używana (wartość źródło s ) |
---|---|
Boolean , boolean | Boolean.valueOf(s) |
char , Character | s.toString.charAt(0) |
inny typ pierwotny lub typ opakowania | Właściwy sposób typu docelowej w przypadku, gdy s jest Number The valueOf(s.toString()) dla typu owijającego inaczej |
BigInteger | BigInteger.valueOf(s.longValue()) jest s to Number , new BigInteger(s.toString()) inaczej |
BigDecimal | BigDecimal.valueOf(s.doubleValue()) jest s to Number , new BigDecimal(s.toString()) inaczej |
Numer | Double.valueOf(s.toString()) jeśli s.toString() zawiera . , Long.valueOf(s.toString()) przeciwnym razie |
Class | Class.forName(s.toString()) wywołany przy użyciu kontekstowego ClassLoader bieżącego wątku bez inicjowania klasy |
wyliczanie | Wynik metody valueOf , dodatkowo przekonwertowanej na String wielkich liter oddzielony przez _ wstawiony przed każdą wielką literą, jeżeli s jest String rozpoczynającym się od małej litery |
inny | wartość zwrócona przez static metodę valueOf w parametrze targetType, która ma parametr pasujący do typu s lub nadklasy tego typu |
Uwaga: To zachowanie nie jest dobrze udokumentowane i może ulec zmianie.
Przykład
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;
}
}
Wydrukowanie wyniku ładowania poniższego pliku fxml
daje
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>