Kihagyás

4. gyakorlat

Korábban láttuk, hogy hogyan lehet a JavaFX API-t felhasználni arra, hogy grafikus felhasználói felületeket hozzunk létre. Ebben a fejezetben azt nézzük meg, hogy hogyan lehet ezt másképpen deklaratív módon megadni, melyért az FXML felelős.

FXML

Az FXML nem más, mint egy XML alapú leíró nyelv, mely segítségével JavaFX alkalmazások esetén a felhasználói felületek szerkezete leírható, az alkalmazás kódtól teljesen elkülönítve, tehát az FXML felelős a mit kérdésre válaszolni, de a viselkedést már nem itt adjuk meg hanem Java kódban.

Tekintsük a következő JavaFX API-val összerakott felületet!

1
2
3
4
5
6
import javafx.scene.layout.VBox;
import javafx.scene.control.Label;
import javafx.scene.control.Button;

VBox root = new VBox();
root.getChildren().addAll(new Label("FXML is awesome"), new Button("Yeah"));

Ennek a felületnek az FXML megfelelője:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>

<VBox>
    <children>
        <Label text="FXML is awesome"/>
        <Button text="Yeah"/>
    </children>
</VBox>

FXML állományokat a *.fxml kiterjesztésű fájlokban tároljuk.

Az első sort már ismerjük (prolog sor), mely után az importok következnek, majd maguk grafikus komponensek.

Megjegyzés

Az FXML általánosabb értelemben is használható, mivel az egy szerializálási formája a Java objektumoknak, azaz nem csak a JavaFX GUI elemeinek leírására használható, hanem tetszőleges Java objektumok állapotának mentésére.

Amennyiben a fenti XML alapú fájlt manuálisan kellene szerkesztenünk, akkor nyilván inkább a JavaFX API-t választanánk nagy többséggel, ugyanakkor az egyszerű formája miatt lehetőség van arra, hogy különböző eszközökkel grafikusan állítsuk elő a felhasználói interfészeket. Erre a célra készült a SceneBuilder, melynek segítségével "összehúzogathatjuk" a grafikus elemeket (WYSWYG eszköz).

A SceneBuilder-t az IntelliJ alapból felkínálja az *.fxml kiterjesztésű fájlok szerkesztéséhez, ám a telepített eszköz futtatható fájlját be kell állítani a Settings->Languages&Frameworks->JavaFX->Path to SceneBuilder segítségével. Ezután tetszőleges FXML fájlon jobb kattintás, és a legalsó menüpont a SceneBuilder, ami megnyitja az állományt a vizuális szerkesztővel.

SceneBuilder main page

Megjegyzés

Az IntelliJ-n belül is lehet használni a SceneBuilder-t, mint bundle-t, de ekkor néhány funkcióhoz nem férünk hozzá.

Az FXML alapjait egy kis példa feladaton fogjuk bemutatni.

Feladat (01_fxml-basic)

Hozzuk létre a SceneBuilder segítségével az alábbi felhasználói felületet! A nyomógomb megnyomására a szövegmezőben lévő szöveg íródjon ki konzolra.

alt text

Feladat lépései:

  1. Induljunk ki egy üres JavaFX projektből (amennyiben archetype-ból indulunk ki, akkor töröljünk ki mindent)!
  2. Hozzunk létre egy sample.fxml fájlt a src/main/resources/hu/alkfejl mappában!
  3. Nyissuk meg SceneBuilder-ben a fájlt (jobb klikk az fxml fájlon -> Open In SceneBuilder)
  4. Állítsuk össze az ablakot (HBox-on belül egy gomb és egy TextField)
  5. Cseréljük a gomb feliratát (Properties fül alatt), és állítsunk be fx:id-t az elemeknek (Inspector -> Code)
  6. Mentsük el, és tanulmányozzuk a generált fxml-t!
  7. Az App.java-ban töltsük be az FXML-et, és cseréljük le a SceneGraph-ot a betöltöttre
  8. Adjuk meg controller-t class-nak a hu.alkfejl.App osztályt
  9. Vegyünk fel egy handleEcho eseménykezelőt a gombhoz
  10. Implementáljuk a metódust úgy, hogy gombnyomás hatására a szövegmező tartalma képernyőre íródjon (FXML dependency pom.xml-be, és FXML annotációk)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>


<HBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="hu.alkfejl.App">
   <children>
      <Button fx:id="myButton" mnemonicParsing="false" text="Button" onAction="#buttonClick" />
      <TextField fx:id="myTextF" />
   </children>
</HBox>

Ez a kódrészlet annak felel meg, mintha létrehoznánk egy HBox, egy Button és egy TextField komponenst, és a Buttont-t, valamint a TextField-et hozzáadnánk a HBox gyerekeihez.

A generált FXML eléggé intuitív ahhoz, hogy értelmezni lehessen, ugyanakkor néhány elemet érdemes közelebbről megnéznünk.

Az import sorok tervezési időben informálják a SceneBuilder-t vagy a betöltéskor az FXMLLoader-t, hogy mely JavaFX osztályokat kell behúzni, kvázi egyenértékű a sima Java-s importtal.

Figyelem

Az FXML tartalma nincs séma által validálva (XSD). A használt XML namespace-ekre azért van szükség, hogy azok segítsék az FXMLLoader, a SceneBuilder, illetve az IDE-k működését.

A példában az fx prefixet két helyen használjuk:

  • fx:controller: A legfelső elemen adhatjuk meg, mely futás időben informálja az alkalmazásunkat, hogy melyik controller-el működik együtt ez az FXML állomány. Itt tudjuk megadni majd az eseménykezelő metódusokat az adott elemekhez, mármint a tényleges metódus definíciókat. Fontos, hogy az itt megadott kontroller osztályt az FXMLLoader példányosítja.
  • fx:id: Minden elemhez hozzárendelhető, mely egyedileg azonosítja az FXML-ben megadott Node-ot. Betöltés után a Node-okat ezen a néven érhetjük el (ilyen nevű field-eket kell majd használnunk a controller-ben is, ha szeretnénk az adott Node-ot a Java kódból is használni, máskülönben elhagyható mind a field, mind az fx:id megadás).

Az FXML-ben az eseménykezelőket is megadhatjuk, melyek definíciója a controller-ben van. A JavaFX API-tól eltérően itt a set előtagot nem kell kiírnunk. Tehát például a setOnAction()-ből csak szimplán onAction attribútum lesz az FXML-en belül. Az attribútumban az eseménykezelő metódus nevét kell megadnunk, illetve az elé egy # jelet is el kell helyeznünk (amennyiben az eseménykezelő definíciója magában az FXML-ben van megadva, például, fx:script tag-ek között, akkor nem kell kiírni a # prefixet).

1
onAction="#buttonClick"

A megadott metódusoknak alapvetően illeszkedniük kell az EventHandler<T> interfész void handle(T event) metódusához, ahol a T típus az eseménynek megfelelően van megadva (gombnyomás esetén ActionEvent, egéresemények esetén MouseEvent, stb.). Az event paraméter el is hagyható abban az esetben, ha nincs rá szükségünk.

Megjegyzés

Az eseménykezelőket nem csak a controllerben tudjuk megadni. Lehetőség van arra is, hogy magában az FXML-ben adjunk meg eseménykezelőket:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?language JavaScript?>
<?import javafx.scene.control.Button?>

<fx:script>
    function f1() {
        java.lang.System.out.println("f1");
    };

    function f2() {
        java.lang.System.out.println("f2");
    };
</fx:script>
<Button text="Close" onAction="f1();"/>

Controller

Az FXML-ben összeállított user interface elmeivel természetesen továbbra is szeretnénk az alkalmazásból kommunikálni, de hogyan érhetjük el őket Java kódból, ha az elemeket FXML-ből dinamikusan tölti be az FXMLLoader, és nem mi hoztuk kézzel létre a kódban a megfelelő GUI komponens típusú változókat? A megoldás az @FXML annotáció használata, mely a field neve alapján és az FXML-ben található fx:id alapján kezdeményezi az összeköttetést. Amennyiben a controller-ben ellátunk field-eket/metódusokat a @FXML marker annotációval, akkor a field-eket/metódusokat a SceneBuilder-ben is látni fogjuk és megadhatjuk őket id-ként, illetve eseménykezelőként.

Megjegyzés

Amit a @FXML annotációval ellátunk nem szükséges, hogy public láthatósággal bírjon. Az annotáció alkalmazása az FXML-ben láthatóvá teszi akár a private field-eket és metódusokat is.

1
2
3
4
@FXML
TextField myTextF;
...
myTextF.getText()

Az FXML és a kód közti kapcsolatot az annotáció teremti meg, ám ahhoz hogy automatikusan tudja melyik FXML-ben deklarált TextField-et kell a változóval összekötni, a változó nevének meg kell egyeznie az FXML-ben deklarált elem fx:id attribútumával!

Az FXML annotáció használatához ráadásul egy új függőséget is fel kell vennünk a POM.xml-be:

1
2
3
4
5
<dependency>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-fxml</artifactId>
    <version>11</version>
</dependency>

FXML betöltés

Az FXML-ben leírt user interface-eket be kell tölteni az alkalmazás kódban, hogy az ott leírt felület létrejöjjön, és a SceneGraph előálljon belőle, amit aztán a szokásos módon meg tudunk jeleníteni egy Stage tartalmaként. Erre a feladatra a Java kódban az FXMLLoader-t használjuk.

1
2
3
4
5
6
7
8
9
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root = loader.load();
Scene scene = new Scene(root, 300, 275);

MyController controller = loader.getController();

setTitle("FXML Welcome");
setScene(scene);
show();

Tipp

A sample.fxml fájlnak elérhetőnek kell lennie a class mellett, amit legegyszerűbben úgy tudunk elérni, hogy az FXML állományokat az src/main/resources mappa alá gyűjtjük ugyanolyan mappa szerkezetben, amilyenben a betöltést végző osztály csomag hierarchiája megkövetel.

A betöltött FXML tartalmat aztán felhasználhatjuk egy az egyben Scene Graph-ként (mivel, hogy az is) és mint ahogyan a fenti példában akár lehet ez a root node-ja egy Scene-nek, de az is előfordulhat, hogy csak egy részfát töltünk be egy FXML-ből és az része lesz egy komplexebb felületnek (újrafelhasználásnál jól jöhet). Fontos, hogy az FXML állományokat futás közben tölti be az FXMLLoader, így csak ebben a pillanatban tudjuk meg, hogy az adott állomány megfelel-e az FXML specifikációjának.

Az FXMLLoader-nek van statikus load metódusa is, de javallott a fent bemutatott módon használni, azaz példányosítani, mivel így kérhetünk referenciát a controller-re is, mely sokszor jó szolgálatot tesz. Ez azt is jelenti, hogy direktben nekünk nem kell példányosítanunk a controller-t, mivel azt elvégzi helyettünk az FXMLLoader.

A halandó embereknek általában elég lesz ez a sima konstruktor, mely az FXML állományra várja az URL hivatkozást, de érdemes lehet a többi konstruktort is megnézni, mert van például, ami képes ResourceBundle-t fogadni, mely a többnyelvű alkalmazások írását támogatja. Például:

1
2
3
Locale locale = new Locale("en");
ResourceBundle bundle = ResourceBundle.getBundle("messages", locale); // messages_en.properties állományt tölti be
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"), bundle);

Ezután az FXML-ben található text-eknél megadhatunk %_msg_id alakú attribútumokat, ahol msg_id a nyelvi fájlokban az adott kulcs értéke. Például:

1
<Button fx:id="secondaryButton" text="%switch.to.primary" onAction="#switchToPrimary" />

mely szöveg megfelelője a messages_en.properties fájlban:

1
switch.to.primary=Switch to Primary View

A ResourceBundle-on felül megadhatunk a builderFactory-t, melyet az FXMLLoader használ a különböző típusú Node-ok létrehozásakor, illetve ennek megfelelően az alapértelmezett callback-et is felülírhatjuk, mely a controller visszaadását valósítja meg (alapvetően a paraméteres konstruktorral példányosítja a controller-t a rendszer, de így felül tudjuk ezt is írni). Ezen felül még az FXML dokumentumok parse-olásakor használt karakterkódolást is megadhatjuk (alapértelmezett UTF-8), továbbá a betöltéshez használandó egyéb loader-eket. A fenti sorrendben megadott lehetséges paraméterekkel a legbővebb konstruktor a következő módon néz ki:

1
2
3
4
5
6
7
8
FXMLLoader(
    URL location,
    ResourceBundle resources,
    BuilderFactory BuilderFactory builderFactory,
    Callback<Class<?>, Object> controllerFactory,
    Charset charset,
    LinkedList<FXMLLoader> loaders
)

Az első két paramétert használjuk a leggyakrabban, a többit csak nagyon speciális esetben kell használnunk.

Az FXMLLoader objektumokon a fenti konstruktor paraméterek alapján getter és setter metódusok is segítik a munkát, azaz egy-egy elem később is beállítható a hozzátartozó setter-el (pl.: setLocation(URL url)), illetve később lekérhető az értéke (pl.: getCharset()).

Megjegyzés

Az FXMLLoader load() metódusa generikus, mely segítségével megadhatjuk a root node típusát. Például:

1
HBox rootNode = loader.<HBox>load();

Az FXMLLoader továbbá támogatja az InputStream-ből történő beolvasást is.

Az FXMLLoader használata során, amikor betöltjük az FXML dokumentumot, akkor sokszor szükség lehet arra, hogy az FXML-ben megadott grafikus elemeknek valamilyen kezdőértéket adjunk. Amennyiben a konstruktorban próbáljuk beállítani az értékeket, akkor nagy valószínűséggel NullPointerException-el találjuk szembe magunkat, mivel a field injektálások még nem történnek meg ilyenkor. Szerencsére van egy egyszerű megoldás erre a problémára, mivel az FXMLLoader automatikusan meghívja az initialize() metódust, akkor amikor a field injektálások megtörténtek. Az initailize() metódus felüldefiniálásához implementáljuk a javafx.fxml.Initializable interfészt!

1
2
3
4
5
6
7
public class PrimaryController implements Initializable {

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // itt lehet a felületi vezérlők inicializálását elvégezni        
    }
}

Feladat

Hozzunk létre egy új projektet IntelliJ-n belül, melynek során a javafx-archetype-fxml-t adjuk meg mint projekt archetype! Próbáljuk meg megérteni a kapott projekt vázat!

Megoldás

FXML archetype

FXML nyalánkságok

A következőkben olyan FXML feature-ök kerülnek bemutatásra, amelyek megkönnyíthetik a munkánkat, de nélkülük is teljes életet élhetünk.

Újrafelhasználható objektumok megadása

Az FXML-ben megadhatunk olyan elemeket, amiket többször is szeretnénk felhasználni. Ezeket az fx:define között kell megadnunk, majd később az fx:id-ban megadott azonosítót használhatjuk fel úgy, hogy az elé egy $ jelet rakunk.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.control.RadioButton?>

<VBox fx:controller="hu.alkfejl.Test" xmlns:fx="http://javafx.com/fxml">
    <fx:define>
        <Insets fx:id="margin" top="5.0" right="5.0" bottom="5.0" left="5.0"/>
        <ToggleGroup fx:id="genderGroup"/>
    </fx:define>

    <Label text="Gender" VBox.margin="$margin"/>
    <RadioButton text="Male" toggleGroup="$genderGroup"/>
    <RadioButton text="Female" toggleGroup="$genderGroup"/>
    <RadioButton text="Unknown" toggleGroup="$genderGroup" selected="true"/>
    <Button text="Close" VBox.margin="$margin"/>
</VBox>

A $-al kezdődő megadásokat a rendszer objektum referenciaként kezeli. Amennyiben a $ az azonosító részét képezi akkor escape-eljük \$-al!

Location megadása

A @-al kezdődő attribútum értékek mindig egy helyre (fájlra, pl.: egy képre, vagy FXML állományra) mutatnak. A @/-el kezdődő megadás a classpath-hoz viszonyított relatív megadási mód. Amennyiben / nélkül használjuk akkor az adott FXML fájlhoz viszonyított relatív megadási módot követünk.

A következő példában egy ImageView-ban megjeleníteni kívánt kép URL-jét adjuk meg (classpath-hoz viszonyítva):

1
2
3
<ImageView>
    <Image url="@/resources/images/logo.jpg"/>
</ImageView>

FXML include

Az FXML dokumentumok között használhatunk include-ot is, azaz az egyik FXML dokumentumból használhatunk egy másikat include formájában. Ennek formája a következő:

1
<fx:include source="other_fxml_doc"/>

A szabály az, hogy minden FXML dokumentumnak egy kontrollere lehet. Amikor egymásba ágyazunk FXML dokumentumokat, akkor is mindegyiknek lehet saját kontroller osztálya.

Konstansok használata

Az FXML-ben felhasználhatjuk a Java osztályokban megadott static final konstansok értékét. Ezeket az értékeket az fx:constant-al hivatkozhatjuk. Például a Long osztály MAX_VALUE értékét a következőképpen használhatjuk:

1
<Long fx:constant="MAX_VALUE"/>

Egy nagyobb példaként tekintsük a következőt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<VBox xmlns:fx="http://javafx.com/fxml">
    <fx:define>
        <Integer fx:constant="MAX_VALUE" fx:id="maxInt"/>
    </fx:define>
    <alignment><Pos fx:constant="CENTER"/></alignment>

    <TextField text="$maxInt"/>
    <TextField>
        <text><Long fx:constant="MIN_VALUE"/></text>
    </TextField>
</VBox>

A példában egy VBox-ban két TextField-et jelenítünk meg, az egyiknek az Integer legkisebb értékét adjuk szövegként, míg a másiknak a legnagyobbat. Ezen felül láthatunk példát a VBox alignment tulajdonságának beállítására is.

FXML elemek hivatkozása

Egy FXML dokumentumon belül hivatkozhatunk az elemekre az fx:reference elem használatával.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<VBox xmlns:fx="http://javafx.com/fxml">
    <fx:define>
        <Image url="resources/images/test.jpg" fx:id="myImg"/>
    </fx:define>
    <ImageView>
        <image>
            <fx:reference source="myImg"/>
        </image>
    </ImageView>
</VBox>

A fenti példában az ImageView-ban megjelenítendő képet egy referenciával adjuk meg. A fenti kód ekvivalens a következővel:

1
2
3
4
5
6
<VBox xmlns:fx="http://javafx.com/fxml">
    <fx:define>
        <Image url="resources/images/test.jpg" fx:id="myImg"/>
    </fx:define>
    <ImageView image="$myImg"/>
</VBox>

Elemek másolása

Előfordulhat, hogy egy FXML elemet le szeretnénk másolni az összes attribútumával együtt. Erre az <fx:copy>-t használhatjuk.

1
<fx:copy source="fx:id of the source object" />

Ahhoz, hogy a copy működjön az adott osztálynak rendelkeznie kell egy copy konstruktorral, azaz olyan konstruktorral, amely az adott osztály egy objektumát várja. Például:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Person {

    private Long id;
    private String name;

    public Person() {
    }

    // The copy constructor
    public Person(Person person) {
        this.id = person.id + 100;
        this.name = person.name + " (Copied)";
    }
    ...
}

Stíluslapok használata

Ahogyan a webes környezetben is, JavaFX-ben is lehetőség van, hogy a megjelenést, a formázást személyre szabjuk és ezen szabályok összességét leválasszuk magukról a UI elemekről és egy szeparált helyen adjuk meg őket. A nagyszerű az egészben, hogy nem kell egy új dolgot megtanulni, mivel használhatunk CSS stíluslapokat a formázások megadásához.

Egy példa, amely már JavaFX-ben használható (és egy kis emlékeztető):

1
2
3
4
.button {
    -fx-background-color: red;
    -fx-text-fill: white;
}

A .button az úgynevezett selector, melyhez property-value párosokat adhatunk meg a kívánt formázások elérésének érdekében. A fő különbség, hogy a property-k megadásánál a -fx- prefixet is meg kell adnunk!

A fent megadott stíluslapot mentsük el például resources/css/style.css néven! Ezután válasszuk ki azt a Scene-t, amelyikre szeretnénk az adott CSS-t alkalmazni és a következő kóddal tegyük is ezt meg!

1
scene.getStylesheets().add("/css/style.css");

A scene.getStylesheets egy ObservableList-et ad vissza, melyhez hozzáadhatunk egy vagy több stíluslapot, amit az adott Scene-re értelmezni szeretnénk. A stíluslapok Node-okhoz rendelése azonban nem korlátozódik pusztán a Scene-re. Stíluslapokat rendelhetünk a Parent típusú elemekhez is.

Névkonvenciók

A JavaFX-ben található osztályokból használt objektumokat (összeset) a class-selectorral tudjuk kiválasztani, de csupa kisbetűvel kell írnunk az osztály nevét. Például a Button típusú objektumokat a .button-el.

Amikor összetett neve van egy osztálynak (pl.: ComboBox), akkor szintén csak kisbetűket használunk, de a szavak közé kötőjelet írunk (pl.: combo-box). A Java osztályok használatáról később még részletesebben írunk.

Alapértelmezett stíluslap

A JavaFX egy beépített, alapértelmezett stíluslapot használ, melyet MODENA-nak hívnak. Mi magunk is lekérhetjük és beállíthatjuk az alapértelmezett stíluslapot, melyet alkalmazásszinten fog használni a rendszer.

1
2
Application.setUserAgentStylesheet(Application.STYLESHEET_CASPIAN); // A Caspian volt a régebbi alapértelmezés
Application.getUserAgentStylesheet(); // itt csak egy stíluslap adható meg ezért nem listát ad vissza

A getter adhat vissza null-t is, ami azt jelzi, hogy a rendszer az alapértelmezést használja.

Inline stílus

Az eddig látottak alapján külső stíluslapokat tudunk megadni, ugyanakkor a Node típusú objektumok rendelkeznek inline stílussal is, melyhez a következő metódusok adnak segítséget:

1
2
setStyle(String inlineStyle)
getStyle()

Például egy gomb inline formázásának megadásához:

1
2
Button btn = new Button("Button");
btn.setStyle("-fx-text-fill: red; -fx-font-weight: bold;");

Stílusok prioritása

Egy-egy vezérlőelemre több forrásból is érkezhetnek formázási szabályok (pl.: egy külső stíluslapból, a default stílusból, inline, property-ből, stb.). Tekintsük az alábbi példát:

style.css

1
2
3
.button {
    -fx-font-size: 24px;
}

App.java

1
2
3
4
5
6
Button btn = new Button("Button");
btn.setStyle("-fx-font-size: 16px"); //inline style
btn.setFont(new Font(10)); // property setting

Scene scene = new Scene(btn);
scene.getStylesheets().addAll("resources/css/style.css");

A megadott font-size-ok közül melyiket fogja értelmezni a rendszer? A prioritás a következő (legmagasabbtól a legalacsonyabb prioritásig):

  • Inline style (legmagasabb, azaz felülír minden egyéb helyen megadottat)
  • Szülő stíluslapja
  • Scene stíluslapja
  • JavaFX API segítségével beállított tulajdonságok (btn.setFont(new Font(10));)
  • Alapértelmezett stíluslapok (legkisebb prioritás)

Selector-ok

JavaFX-ben is használhatunk különböző selector-okat, melyek közül a legfontosabbakat vesszük sorra.

Class selector

Minden Node típusú elem rendelkezik a styleClass változóval, melynek típusa ObservableList<String>, azaz egy Node-ot több osztályhoz is hozzárendelhetünk.

Fontos

Ne keverjük össze az objektum osztályát (típusát), a CSS osztállyal!

A class selector hasonlóan működik, mint hagyományos CSS környezetben, azaz a . prefixszel kell ellátnunk ezeket a selector-okat a CSS-ben.

Korábban láttuk, hogy az összes gombot is kiválaszthatjuk ha a .button selector-t használjuk. Ennek oka, hogy a JavaFX sok esetben alapértelmezett styleClass-t is megad az elemekhez, mely az adott objektum típusának kisbetűsített és kötőjelesített változata. Fontos megjegyezni, hogy a konténer osztályokhoz nem rendel hozzá alapértelmezett styleClass-t a rendszer.

Az alapértelmezett styleClass-ok belekerülnek az említett ObservableList<String> típusú listába, így egy adott elemre lekérhetjük azok listáját, pl.: btn.getStyleClass().

id selector

Hasonlóképpen a hagyományos CSS-hez, itt is használhatunk id alapú selector-t, melyhez a # prefixet kell használnunk! Pl.:

1
2
3
#myBtn{
    -fx-font-size: 24px;
}
1
2
3
Button btn = new Button("Push me");
btn.setId("myBtn"); // FXML-ben az fx:id-val is ugyanezt a hatást érhetjük el
...

Egyéb selector-ok

  • JavaFX-ben is használható az univerzális selector, azaz a *, mely minden Node-ra illeszkedik.
  • Több selector csoportosításakor használható a felsorolás, melynek során a selector-ok közé ,-t kell tennünk (.hbox, .button -> hbox és button stílusosztállyal rendelkező elemek együttes kiválasztása)
  • Leszármazott kiválasztásnál a selectorok közé space-t kell tennünk (pl.: .hbox .button -> hbox stílusú elemeken belüli gombok kiválasztása)
  • child selector-t a > használatával alkalmazhatunk (pl.: .hbox > .button -> hbox-on belüli button gyerekek kiválasztása)
  • A testvér (sibling) és az attribútum selector-ok NEM támogatottak JavaFX-ben!
  • állapot alapú selectorok a :-al adhatóak meg (pl.: .button:hover)

A selector-okat nem csak a CSS-ben használhatjuk fel, arra is jók, hogy egy Node keresésénél felhasználjuk őket. A Scene és maga a Node is rendelkezik egy lookup(String selector) metódussal, melynek paraméterként megadható tetszőleges selector, majd a metódus ez alapján visszaadja a megtalált Node-ot. A Scene metódusa a teljes SceneGraph-ban keres, míg a Node-ra hívott az általa tartalmazott Node-ok között keres (magát is beleértve). Amennyiben több Node-ot szeretnénk egyszerre kiválasztani, akkor használhatjuk a lookupAll(String selector) metódust, mely Set<Node>-al tér vissza. Pl.:

1
2
Node node = scene.lookup("#myBtn");
Nodes buttons = scene.lookupAll(".button");

Videók

Referenciák


Utolsó frissítés: 2024-03-06 13:12:22