Programovanie (2) v Jave
1-INF-166, letný semester 2023/24
Letný semester, prednáška č. 10: Rozdiel medzi revíziami
(40 medziľahlých úprav od rovnakého používateľa nie je zobrazených.) | |||
Riadok 1: | Riadok 1: | ||
== Oznamy == | == Oznamy == | ||
− | * | + | * Riešenia druhej domácej úlohy je potrebné odovzdať ''do utorka 30. apríla 2024, 9:50'' – čiže do začiatku jedenástych cvičení. |
− | + | * Počas jedenástych cvičení – čiže ''v utorok 30. apríla 2024 od 9:50 do 11:20'' – bude prebiehať štvrtý test zameraný na látku z prvých desiatich týždňov. Body z testu bude možné získať iba v prípade prítomnosti na cvičeniach v miestnosti I-H6. | |
− | |||
== Lambda výrazy == | == Lambda výrazy == | ||
Riadok 9: | Riadok 8: | ||
=== Príklad: komparátor ako lambda výraz === | === Príklad: komparátor ako lambda výraz === | ||
− | Pripomeňme si z [[Letný semester, prednáška č. 5|piatej prednášky]] ''zostupné'' triedenie zoznamu celých čísel pomocou metódy [https://docs.oracle.com/en/java/javase/ | + | Pripomeňme si z [[Letný semester, prednáška č. 5|piatej prednášky]] ''zostupné'' triedenie zoznamu celých čísel pomocou metódy [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collections.html#sort(java.util.List,java.util.Comparator) <tt>Collections.sort</tt>], ktorá ako druhý parameter berie ''komparátor'', čiže inštanciu nejakej triedy implementujúcej rozhranie [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Comparator.html <tt>Comparator<? super Integer></tt>]. Videli sme pritom tri možné spôsoby, ako komparátor definovať: |
* Vytvoriť „bežnú” triedu implementujúcu rozhranie <tt>Comparator<Integer></tt> a metódu <tt>sort</tt> zavolať pre novovytvorenú inštanciu tejto triedy. | * Vytvoriť „bežnú” triedu implementujúcu rozhranie <tt>Comparator<Integer></tt> a metódu <tt>sort</tt> zavolať pre novovytvorenú inštanciu tejto triedy. | ||
* To isté s použitím ''lokálnej triedy'': | * To isté s použitím ''lokálnej triedy'': | ||
Riadok 37: | Riadok 36: | ||
* Definíciu triedy a vytvorenie jej inštancie spojiť do jediného príkazu s využitím mechanizmu ''anonymných tried'': | * Definíciu triedy a vytvorenie jej inštancie spojiť do jediného príkazu s využitím mechanizmu ''anonymných tried'': | ||
:<syntaxhighlight lang="java"> | :<syntaxhighlight lang="java"> | ||
− | |||
− | |||
− | |||
− | |||
import java.util.*; | import java.util.*; | ||
Riadok 65: | Riadok 60: | ||
Aj posledný z uvedených spôsobov definície komparátora je však stále trochu ťažkopádny – jediná pre účely triedenia podstatná informácia je tu daná riadkom <tt>return o2.compareTo(o1);</tt>, pričom zvyšok konštrukcie by bol rovnaký aj pri definícii ľubovoľného iného komparátora. | Aj posledný z uvedených spôsobov definície komparátora je však stále trochu ťažkopádny – jediná pre účely triedenia podstatná informácia je tu daná riadkom <tt>return o2.compareTo(o1);</tt>, pričom zvyšok konštrukcie by bol rovnaký aj pri definícii ľubovoľného iného komparátora. | ||
− | Rozhranie <tt>Comparator<E></tt> je príkladom takzvaného ''funkcionálneho rozhrania'' – čiže rozhrania, ktoré deklaruje ''jedinú abstraktnú metódu''; v tomto prípade ide o metódu [https://docs.oracle.com/en/java/javase/ | + | Rozhranie <tt>Comparator<E></tt> je príkladom takzvaného ''funkcionálneho rozhrania'' – čiže rozhrania, ktoré deklaruje ''jedinú abstraktnú metódu''; v tomto prípade ide o metódu [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Comparator.html#compare(T,T) <tt>compare</tt>]. Toto rozhranie síce definuje niekoľko ďalších statických metód a metód s modifikátorom <tt>default</tt>; jedinou metódou, ktorú je potrebné implementovať kedykoľvek implementujeme rozhranie <tt>Comparator<E></tt>, je ale metóda <tt>compare</tt>. To okrem iného znamená, že proces vytvárania tried implementujúcich rozhranie <tt>Comparator<E></tt> sa často môže redukovať iba na implementáciu tejto jednej metódy. |
Ako skrátený zápis pre definíciu inštancie anonymnej triedy ''implementujúcej funkcionálne rozhranie'', obsahujúcej iba definíciu jedinej abstraktnej metódy deklarovanej v tomto rozhraní, slúžia v Jave takzvané ''lambda výrazy''. Utriedenie poľa tak možno realizovať pomocou príkazu | Ako skrátený zápis pre definíciu inštancie anonymnej triedy ''implementujúcej funkcionálne rozhranie'', obsahujúcej iba definíciu jedinej abstraktnej metódy deklarovanej v tomto rozhraní, slúžia v Jave takzvané ''lambda výrazy''. Utriedenie poľa tak možno realizovať pomocou príkazu | ||
Riadok 76: | Riadok 71: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
V zátvorkách pred šípkou sú postupne uvedené identifikátory argumentov metódy <tt>compare</tt> a za šípkou nasleduje blok obsahujúci telo metódy <tt>compare</tt>, prípadne výraz udávajúci výstupnú hodnotu tejto metódy. | V zátvorkách pred šípkou sú postupne uvedené identifikátory argumentov metódy <tt>compare</tt> a za šípkou nasleduje blok obsahujúci telo metódy <tt>compare</tt>, prípadne výraz udávajúci výstupnú hodnotu tejto metódy. | ||
+ | |||
+ | ''Poznámka'': pripomeňme si tiež z piatej prednášky, že uvedený príklad je čisto ilustračný – rovnako sa správajúci komparátor možno získať aj volaním statickej generickej metódy <tt>Comparator.<Integer>reverseOrder()</tt> resp. skrátene <tt>Comparator.reverseOrder()</tt>. | ||
=== Syntax lambda výrazov v Jave === | === Syntax lambda výrazov v Jave === | ||
Riadok 90: | Riadok 87: | ||
(arg1, arg2, ..., argn) -> { /* Telo implementacie metody f */ } | (arg1, arg2, ..., argn) -> { /* Telo implementacie metody f */ } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | V prípade, že je počet argumentov metódy <tt>f</tt> nulový, píšeme | + | Pred argumentmi možno nepovinne uvádzať aj ich typy (buď sa typ uvedie pri všetkých argumentoch, alebo sa neuvedie pri žiadnom argumente). V prípade, že je počet argumentov metódy <tt>f</tt> nulový, píšeme |
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
() -> { /* Telo implementacie metody f */ } | () -> { /* Telo implementacie metody f */ } | ||
Riadok 99: | Riadok 96: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | V prípade, že návratový typ <tt>T</tt> metódy <tt>f</tt> nie je <tt>void</tt>, možno na pravej strane lambda výrazu namiesto bloku obsahujúceho telo implementovanej metódy uviesť iba výraz, ktorý sa vyhodnotí na typ <tt>T</tt>; v takom prípade bude metóda <tt>f</tt> počítať tento výraz. V prípade, že návratovým typom metódy <tt>f</tt> je <tt>void</tt>, možno namiesto bloku s telom implementovanej metódy uviesť volanie jednej metódy s návratovým typom <tt>void</tt> (ktoré možno z určitého pohľadu chápať ako „výraz typu <tt>void</tt>”). | + | V prípade, že návratový typ <tt>T</tt> metódy <tt>f</tt> nie je <tt>void</tt>, možno na pravej strane lambda výrazu namiesto bloku obsahujúceho telo implementovanej metódy uviesť iba výraz, ktorý sa vyhodnotí na typ <tt>T</tt>; v takom prípade bude metóda <tt>f</tt> počítať tento výraz. V prípade, že návratovým typom metódy <tt>f</tt> je <tt>void</tt>, možno namiesto bloku s telom implementovanej metódy uviesť volanie jednej metódy s návratovým typom <tt>void</tt> (ktoré možno z určitého pohľadu chápať ako „výraz typu <tt>void</tt>”), prípadne volanie jednej metódy s iným návratovým typom, pričom sa však návratová hodnota tejto metódy odignoruje. |
Anotácia <tt>@FunctionalInterface</tt> používaná v nasledujúcich príkladoch je nepovinná (všetko by rovnako dobre fungovalo aj bez nej), ale odporúčaná – kompilátor totiž vyhodí chybu kedykoľvek je táto anotácia použitá inde ako pri funkcionálnom rozhraní. | Anotácia <tt>@FunctionalInterface</tt> používaná v nasledujúcich príkladoch je nepovinná (všetko by rovnako dobre fungovalo aj bez nej), ale odporúčaná – kompilátor totiž vyhodí chybu kedykoľvek je táto anotácia použitá inde ako pri funkcionálnom rozhraní. | ||
Riadok 172: | Riadok 169: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
+ | Rovnako ako z lokálnych a anonymných tried, možno aj z definícií lambda výrazov pristupovať k premenným inštancií, či k finálnym alebo „v podstate finálnym” lokálnym premenným metód, ktorých sú súčasťou. | ||
=== Ďalší príklad použitia lambda výrazov === | === Ďalší príklad použitia lambda výrazov === | ||
− | Rozhranie [https://docs.oracle.com/en/java/javase/ | + | Rozhranie [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Iterable.html <tt>Iterable<T></tt>] deklaruje okrem iného aj metódu [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Iterable.html#forEach(java.util.function.Consumer) <tt>forEach</tt>], ktorá postupne pre všetky prvky vracané iterátorom vykoná danú akciu a predstavuje tak alternatívu k použitiu cyklu <tt>for each</tt>. Akcia, ktorá sa má pre všetky prvky vykonať, je daná inštanciou nejakej triedy implementujúcej funkcionálne rozhranie [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/function/Consumer.html <tt>Consumer<T></tt>] z balíka <tt>java.util.function</tt>. V ňom je deklarovaná jediná abstraktná metóda [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/function/Consumer.html#accept(T) <tt>accept</tt>] s návratovým typom <tt>void</tt> a jediným argumentom typu <tt>T</tt>. Metóda <tt>forEach</tt> zavolá túto metódu <tt>accept</tt> postupne pre všetky prvky vracané iterátorom. |
''Príklad'': | ''Príklad'': | ||
Riadok 210: | Riadok 209: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | Viacero ďalších funkcionálnych rozhraní, ktorých inštancie možno definovať pomocou lambda výrazov, je definovaných v balíku [https://docs.oracle.com/en/java/javase/ | + | Viacero ďalších funkcionálnych rozhraní, ktorých inštancie možno definovať pomocou lambda výrazov, je definovaných v balíku [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/function/package-summary.html <tt>java.util.function</tt>]. |
=== Referencie na metódy === | === Referencie na metódy === | ||
Riadok 235: | Riadok 234: | ||
* Pokyny k inštalácii JavaFX, ako aj návod na skompilovanie a spustenie prvého programu z IntelliJ IDEA a z príkazového riadku možno nájsť [[Letný semester, softvér|tu]]. | * Pokyny k inštalácii JavaFX, ako aj návod na skompilovanie a spustenie prvého programu z IntelliJ IDEA a z príkazového riadku možno nájsť [[Letný semester, softvér|tu]]. | ||
− | * [https://openjfx.io/javadoc/ | + | * [https://openjfx.io/javadoc/21/ Dokumentácia k JavaFX 21 API]. |
* Ďalšie dokumentácie a tutoriály možno nájsť na [https://openjfx.io/ stránke projektu]. | * Ďalšie dokumentácie a tutoriály možno nájsť na [https://openjfx.io/ stránke projektu]. | ||
Riadok 267: | Riadok 266: | ||
Uvedený kód si teraz rozoberme: | Uvedený kód si teraz rozoberme: | ||
− | * Hlavná trieda JavaFX aplikácie (tzn. trieda obsahujúca metódu <tt>main</tt>) sa vyznačuje tým, že dedí od abstraktnej triedy <tt>[https://openjfx.io/javadoc/ | + | * Hlavná trieda JavaFX aplikácie (tzn. trieda obsahujúca metódu <tt>main</tt>) sa vyznačuje tým, že dedí od abstraktnej triedy <tt>[https://openjfx.io/javadoc/21/javafx.graphics/javafx/application/Application.html Application]</tt> definovanej v balíku <tt>javafx.application</tt>, ktorý je potrebné importovať. |
− | * Každá trieda dediaca od triedy <tt>Application</tt> musí implementovať jej abstraktnú metódu <tt>start</tt>, ktorej argumentom je objekt <tt>primaryStage</tt> typu <tt>[https://openjfx.io/javadoc/ | + | * Každá trieda dediaca od triedy <tt>Application</tt> musí implementovať jej abstraktnú metódu <tt>start</tt>, ktorej argumentom je objekt <tt>primaryStage</tt> typu <tt>[https://openjfx.io/javadoc/21/javafx.graphics/javafx/stage/Stage.html Stage]</tt> reprezentujúci hlavné grafické okno aplikácie (trieda <tt>Stage</tt> je definovaná v balíku <tt>javafx.stage</tt>, ktorý je potrebné importovať). V rámci metódy <tt>start</tt> sa vykonávajú úkony, ktoré sa majú udiať hneď po spustení aplikácie – typicky sa vytvárajú jednotlivé grafické ovládacie prvky aplikácie a špecifikujú sa ich vlastnosti. |
** V našom prípade je kľúčovým riadkom metódy <tt>start</tt> volanie <tt>primaryStage.show()</tt>, ktorým zobrazíme hlavné okno aplikácie. Bez tohto volania by aplikácia bežala „na pozadí”. | ** V našom prípade je kľúčovým riadkom metódy <tt>start</tt> volanie <tt>primaryStage.show()</tt>, ktorým zobrazíme hlavné okno aplikácie. Bez tohto volania by aplikácia bežala „na pozadí”. | ||
** Volaním <tt>primaryStage.setTitle("Hello, World!")</tt> nastavíme titulok hlavného okna na text „''Hello, World!''”. | ** Volaním <tt>primaryStage.setTitle("Hello, World!")</tt> nastavíme titulok hlavného okna na text „''Hello, World!''”. | ||
** Uvedené dva riadky často stačia na zobrazenie grafického okna s titulkom „''Hello, World!''” a „náhodne” zvolenou veľkosťou. V závislosti od systému sa však môže stať aj to, že sa nezobrazí nič – grafické okno totiž zatiaľ nič neobsahuje a systém nemá ako „rozumne” vypočítať jeho veľkosť; môže teda túto situáciu vyhodnotiť aj tak, že ešte nie je čo zobraziť. | ** Uvedené dva riadky často stačia na zobrazenie grafického okna s titulkom „''Hello, World!''” a „náhodne” zvolenou veľkosťou. V závislosti od systému sa však môže stať aj to, že sa nezobrazí nič – grafické okno totiž zatiaľ nič neobsahuje a systém nemá ako „rozumne” vypočítať jeho veľkosť; môže teda túto situáciu vyhodnotiť aj tak, že ešte nie je čo zobraziť. | ||
** Zvyšnými riadkami už len hovoríme, že „obsahom” hlavného okna má byť prázdna oblasť o veľkosti 300 krát 250 pixelov: | ** Zvyšnými riadkami už len hovoríme, že „obsahom” hlavného okna má byť prázdna oblasť o veľkosti 300 krát 250 pixelov: | ||
− | *** Kontajnerom pre obsah okna je inštancia triedy <tt>[https://openjfx.io/javadoc/ | + | *** Kontajnerom pre obsah okna je inštancia triedy <tt>[https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/Scene.html Scene]</tt>. Ide tu o analógiu s divadelnou terminológiou: okno zodpovedá javisku; na javisku následne možno umiestniť scénu pozostávajúcu z jednotlivých rekvizít. Scénu <tt>scene</tt> možno oknu <tt>primaryStage</tt> priradiť volaním <tt>primaryStage.setScene(scene)</tt>. Trieda <tt>Scene</tt> je definovaná v balíku <tt>javafx.scene</tt>, ktorý je potrebné importovať. |
*** Scéna je daná predovšetkým hierarchickým stromom uzlov (detaily neskôr), pričom uzlami môžu byť napríklad oblasti, ale aj ovládacie prvky ako napríklad tlačidlá, či textové polia. Volaním konštruktora <tt>Scene scene = Scene(pane, 300, 250)</tt> vytvoríme scénu o rozmeroch 300 krát 250 pixelov, ktorej koreňovým uzlom je objekt <tt>pane</tt>; ten bude v našom prípade reprezentovať prázdnu oblasť. | *** Scéna je daná predovšetkým hierarchickým stromom uzlov (detaily neskôr), pričom uzlami môžu byť napríklad oblasti, ale aj ovládacie prvky ako napríklad tlačidlá, či textové polia. Volaním konštruktora <tt>Scene scene = Scene(pane, 300, 250)</tt> vytvoríme scénu o rozmeroch 300 krát 250 pixelov, ktorej koreňovým uzlom je objekt <tt>pane</tt>; ten bude v našom prípade reprezentovať prázdnu oblasť. | ||
− | *** Volaním konštruktora <tt>Pane pane = new Pane()</tt> vytvoríme novú oblasť <tt>pane</tt>. Tá môže neskôr slúžiť ako kontajner pre pridávanie rôznych ovládacích prvkov a podobne. Trieda <tt>[https:// | + | *** Volaním konštruktora <tt>Pane pane = new Pane()</tt> vytvoríme novú oblasť <tt>pane</tt>. Tá môže neskôr slúžiť ako kontajner pre pridávanie rôznych ovládacích prvkov a podobne. Trieda <tt>[https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/layout/Pane.html Pane]</tt> je definovaná v balíku <tt>javafx.scene.layout</tt>, ktorý je potrebné importovať. |
− | * Metóda <tt>main</tt> JavaFX | + | * Metóda <tt>main</tt> pri jednoduchých JavaFX aplikáciách typicky pozostáva z jediného riadku, v ktorom sa volá statická metóda <tt>launch</tt> triedy <tt>Application</tt>. Tá sa postará o vytvorenie inštancie našej triedy <tt>HelloFX</tt>, o vytvorenie hlavného grafického okna aplikácie, ako aj o následné zavolanie metódy <tt>start</tt>, ktorá dostane vytvorené okno ako argument. |
=== Okno s niekoľkými jednoduchými ovládacími prvkami === | === Okno s niekoľkými jednoduchými ovládacími prvkami === | ||
− | Podbne ako v príklade vyššie vytvorme aplikáciu pozostávajúcu | + | Podbne ako v príklade vyššie vytvorme aplikáciu pozostávajúcu z jediného grafického okna. |
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
Riadok 310: | Riadok 309: | ||
Pridáme teraz do hlavného okna niekoľko ovládacích prvkov tak, ako na obrázku vpravo. Naším cieľom bude vytvorenie aplikácie umožňujúcej zadať text, ktorý sa pri kliknutí na tlačidlo <tt>OK</tt> zjaví v textovom popisku červenej farby. Ovládacie prvky ako textové pole alebo tlačidlo sú definované v balíku <tt>javafx.scene.control</tt>, ktorý je tak nutné importovať. Podobne na prácu s fontmi budeme potrebovať balík <tt>javafx.scene.text</tt> a na prácu s farbami balík <tt>javafx.scene.paint</tt>. | Pridáme teraz do hlavného okna niekoľko ovládacích prvkov tak, ako na obrázku vpravo. Naším cieľom bude vytvorenie aplikácie umožňujúcej zadať text, ktorý sa pri kliknutí na tlačidlo <tt>OK</tt> zjaví v textovom popisku červenej farby. Ovládacie prvky ako textové pole alebo tlačidlo sú definované v balíku <tt>javafx.scene.control</tt>, ktorý je tak nutné importovať. Podobne na prácu s fontmi budeme potrebovať balík <tt>javafx.scene.text</tt> a na prácu s farbami balík <tt>javafx.scene.paint</tt>. | ||
− | Začnime s pridaním textového popisku „Zadaj text”. Takéto textové popisky sú v JavaFX reprezentované triedou <tt>[https:// | + | Začnime s pridaním textového popisku „Zadaj text”. Takéto textové popisky sú v JavaFX reprezentované triedou <tt>[https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/Label.html Label]</tt>, pričom popisok <tt>label1</tt> obsahujúci nami požadovaný text vytvoríme nasledovne: |
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
+ | import javafx.scene.control.*; | ||
+ | |||
+ | // ... | ||
+ | |||
Label label1 = new Label("Zadaj text:"); | Label label1 = new Label("Zadaj text:"); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Riadok 322: | Riadok 325: | ||
Následne môžeme upraviť niektoré vlastnosti vytvoreného popisku, ako napríklad jeho pozíciu a font: | Následne môžeme upraviť niektoré vlastnosti vytvoreného popisku, ako napríklad jeho pozíciu a font: | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
+ | import javafx.scene.text.*; // Kvoli triede Font. | ||
+ | |||
+ | // ... | ||
+ | |||
label1.setFont(Font.font("Tahoma", FontWeight.BOLD, 12)); | label1.setFont(Font.font("Tahoma", FontWeight.BOLD, 12)); | ||
label1.setLayoutX(20); | label1.setLayoutX(20); | ||
Riadok 329: | Riadok 336: | ||
Analogicky vytvoríme aj ostatné komponenty: | Analogicky vytvoríme aj ostatné komponenty: | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
+ | import javafx.scene.paint.*; // Kvoli triede Color. | ||
+ | |||
+ | // ... | ||
+ | |||
TextField textField = new TextField(); | TextField textField = new TextField(); | ||
pane.getChildren().add(textField); | pane.getChildren().add(textField); | ||
Riadok 356: | Riadok 367: | ||
* Formát jednotlivých ovládacích prvkov (ako napríklad font alebo farba) by sa po správnosti nemal nastavovať priamo v zdrojovom kóde. Namiesto toho je odporúčaným prístupom využitie štýlov definovaných v pomocných súboroch JavaFX CSS. Takto je možné meniť formátovanie bez väčších zásahov do zdrojového kódu. | * Formát jednotlivých ovládacích prvkov (ako napríklad font alebo farba) by sa po správnosti nemal nastavovať priamo v zdrojovom kóde. Namiesto toho je odporúčaným prístupom využitie štýlov definovaných v pomocných súboroch JavaFX CSS. Takto je možné meniť formátovanie bez väčších zásahov do zdrojového kódu. | ||
− | + | Obidva tieto nedostatky napravíme v rámci nasledujúcej prednášky. | |
=== Oživenie ovládacích prvkov (spracovanie udalostí) === | === Oživenie ovládacích prvkov (spracovanie udalostí) === | ||
Riadok 362: | Riadok 373: | ||
Dokončime našu jednoduchú aplikáciu so zadávaním textu pridaním jej kľúčovej funkcionality: po stlačení tlačidla OK (t.j. <tt>button</tt>) sa má do „červeného” popisku prekopírovať text zadaný používateľom do textového poľa. | Dokončime našu jednoduchú aplikáciu so zadávaním textu pridaním jej kľúčovej funkcionality: po stlačení tlačidla OK (t.j. <tt>button</tt>) sa má do „červeného” popisku prekopírovať text zadaný používateľom do textového poľa. | ||
− | Po stlačení tlačidla <tt>button</tt> je systémom | + | Po stlačení tlačidla <tt>button</tt> je systémom vygenerovaná tzv. ''udalosť'', ktorá je v tomto prípade typu <tt>[https://openjfx.io/javadoc/21/javafx.base/javafx/event/ActionEvent.html ActionEvent]</tt>. Udalosť je teda akýsi objekt nesúci informáciu o tom, že bolo stlačené dané tlačidlo. Každé tlačidlo – objekt typu <tt>[https://openjfx.io/javadoc/21/javafx.controls/javafx/scene/control/Button.html Button]</tt> – má navyše k dispozícii (zdedenú) metódu |
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
public final void setOnAction(EventHandler<ActionEvent> value) | public final void setOnAction(EventHandler<ActionEvent> value) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | umožňujúcu „zaregistrovať” pre dané tlačidlo jeho | + | umožňujúcu „zaregistrovať” pre dané tlačidlo jeho spracúvateľa udalostí typu <tt>ActionEvent</tt>. Ním môže byť ľubovoľná trieda implementujúca rozhranie <tt>[https://openjfx.io/javadoc/21/javafx.base/javafx/event/EventHandler.html EventHandler]<ActionEvent></tt>, ktoré vyžaduje implementáciu jedinej metódy |
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
void handle(ActionEvent event) | void handle(ActionEvent event) | ||
Riadok 376: | Riadok 387: | ||
sa po každom stlačení tlačidla <tt>button</tt> vykoná metóda <tt>eventHandler.handle</tt>. | sa po každom stlačení tlačidla <tt>button</tt> vykoná metóda <tt>eventHandler.handle</tt>. | ||
− | Nami požadovanú funkcionalitu tlačidla <tt>button</tt> tak vieme vyjadriť napríklad pomocou lokálnej triedy <tt>ButtonActionEventHandler</tt> | + | Nami požadovanú funkcionalitu tlačidla <tt>button</tt> tak vieme vyjadriť napríklad pomocou lokálnej triedy <tt>ButtonActionEventHandler</tt>. |
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
import javafx.event.*; | import javafx.event.*; | ||
− | ... | + | // ... |
public void start(Stage primaryStage) { | public void start(Stage primaryStage) { | ||
− | ... | + | // ... |
class ButtonActionEventHandler implements EventHandler<ActionEvent> { | class ButtonActionEventHandler implements EventHandler<ActionEvent> { | ||
Riadok 396: | Riadok 407: | ||
button.setOnAction(eventHandler); | button.setOnAction(eventHandler); | ||
− | ... | + | // ... |
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | Skrátene môžeme to isté napísať s použitím anonymnej triedy | + | Skrátene môžeme to isté napísať s použitím anonymnej triedy. |
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
public void start(Stage primaryStage) { | public void start(Stage primaryStage) { | ||
− | ... | + | // ... |
− | button.setOnAction(new EventHandler< | + | button.setOnAction(new EventHandler<>() { |
@Override | @Override | ||
public void handle(ActionEvent event) { | public void handle(ActionEvent event) { | ||
Riadok 414: | Riadok 425: | ||
}); | }); | ||
− | ... | + | // ... |
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | Ak si navyše uvedomíme, že rozhranie | + | Ak si navyše uvedomíme, že je rozhraní <tt>EventHandler</tt> deklarovaná jediná abstraktná metóda – ide teda o ''funkcionálne rozhranie'' – môžeme tento zápis ešte ďalej zjednodušiť použitím lambda výrazu. |
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
public void start(Stage primaryStage) { | public void start(Stage primaryStage) { | ||
− | ... | + | // ... |
− | button.setOnAction( | + | button.setOnAction(event -> label2.setText(textField.getText())); |
− | |||
− | |||
− | ... | + | // ... |
} | } | ||
Riadok 438: | Riadok 447: | ||
=== Geometrické útvary === | === Geometrické útvary === | ||
− | Špeciálnym typom uzlov, ktoré možno umiestňovať do scén, sú geometrické útvary ako napríklad <tt>[https:// | + | Špeciálnym typom uzlov, ktoré možno umiestňovať do scén, sú geometrické útvary ako napríklad <tt>[https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/shape/Circle.html Circle]</tt>, <tt>[https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/shape/Rectangle.html Rectangle]</tt>, <tt>[https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/shape/Arc.html Arc]</tt>, <tt>[https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/shape/Ellipse.html Ellipse]</tt>, <tt>[https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/shape/Line.html Line]</tt>, <tt>[https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/shape/Polygon.html Polygon]</tt>, atď. Všetky útvary dedia od spoločnej abstraktnej nadtriedy <tt>[https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/shape/Shape.html Shape]</tt>. Sú definované v balíku <tt>javafx.scene.shape</tt>, ktorý je nutné na prácu s nimi importovať. |
− | Aj keď útvary nevedia vyvolať udalosť typu <tt>ActionEvent</tt>, môžu vyvolávať udalosti iných typov. Napríklad kliknutie na útvar myšou vyústi v udalosť typu <tt>[https:// | + | Aj keď útvary nevedia vyvolať udalosť typu <tt>ActionEvent</tt>, môžu vyvolávať udalosti iných typov. Napríklad kliknutie na útvar myšou vyústi v udalosť typu <tt>[https://openjfx.io/javadoc/21/javafx.graphics/javafx/scene/input/MouseEvent.html MouseEvent]</tt> (definovanú v balíku <tt>javafx.scene.input</tt>) a spracovávateľa takejto udalosti možno pre útvar <tt>shape</tt> zaregistrovať pomocou metódy <tt>shape.setOnMouseClicked</tt>. |
Nasledujúci kód vykreslí „tabuľku” o 10 krát 10 útvaroch, pričom pre každý sa náhodne určí, či pôjde o štvorec, alebo o kruh. Farba každého z útvarov sa taktiež určí náhodne. Navyše po kliknutí myšou na ktorýkoľvek z útvarov sa jeho farba náhodne zmení. | Nasledujúci kód vykreslí „tabuľku” o 10 krát 10 útvaroch, pričom pre každý sa náhodne určí, či pôjde o štvorec, alebo o kruh. Farba každého z útvarov sa taktiež určí náhodne. Navyše po kliknutí myšou na ktorýkoľvek z útvarov sa jeho farba náhodne zmení. | ||
Riadok 453: | Riadok 462: | ||
import javafx.scene.shape.*; | import javafx.scene.shape.*; | ||
import javafx.scene.paint.*; | import javafx.scene.paint.*; | ||
− | |||
import java.util.*; | import java.util.*; | ||
− | + | ||
public class Aplikacia extends Application { | public class Aplikacia extends Application { | ||
− | Color | + | private Color randomColour(Random random) { |
return Color.color(random.nextDouble(), random.nextDouble(), random.nextDouble()); | return Color.color(random.nextDouble(), random.nextDouble(), random.nextDouble()); | ||
} | } | ||
− | + | ||
@Override | @Override | ||
public void start(Stage primaryStage) { | public void start(Stage primaryStage) { | ||
Pane pane = new Pane(); | Pane pane = new Pane(); | ||
− | + | ||
Random random = new Random(); | Random random = new Random(); | ||
for (int i = 0; i <= 9; i++) { | for (int i = 0; i <= 9; i++) { | ||
for (int j = 0; j <= 9; j++) { | for (int j = 0; j <= 9; j++) { | ||
Shape shape; | Shape shape; | ||
− | + | Color colour = randomColour(random); | |
− | Color | + | if (random.nextBoolean()) { |
− | if ( | + | shape = new Rectangle(j * 60 + 5, i * 60 + 5, 50, 50); |
− | shape = new Rectangle( | ||
} else { | } else { | ||
− | shape = new Circle( | + | shape = new Circle(j * 60 + 30, i * 60 + 30, 25); |
} | } | ||
− | shape.setFill( | + | shape.setFill(colour); |
− | shape.setOnMouseClicked( | + | shape.setOnMouseClicked(event -> shape.setFill(randomColour(random))); |
− | |||
− | |||
pane.getChildren().add(shape); | pane.getChildren().add(shape); | ||
} | } | ||
} | } | ||
− | + | ||
Scene scene = new Scene(pane, 600, 600); | Scene scene = new Scene(pane, 600, 600); | ||
− | + | ||
primaryStage.setTitle("Geometrické útvary"); | primaryStage.setTitle("Geometrické útvary"); | ||
primaryStage.setResizable(false); | primaryStage.setResizable(false); | ||
Riadok 492: | Riadok 497: | ||
primaryStage.show(); | primaryStage.show(); | ||
} | } | ||
− | + | ||
public static void main(String[] args) { | public static void main(String[] args) { | ||
launch(args); | launch(args); | ||
Riadok 503: | Riadok 508: | ||
* [https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html Java tutoriál: Lambda Expressions] | * [https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html Java tutoriál: Lambda Expressions] | ||
* [https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html Java tutoriál: Method References] | * [https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html Java tutoriál: Method References] | ||
− | * [https://openjfx.io/javadoc/ | + | * [https://openjfx.io/javadoc/21/ Dokumentácia k JavaFX] |
Aktuálna revízia z 10:45, 23. apríl 2024
Oznamy
- Riešenia druhej domácej úlohy je potrebné odovzdať do utorka 30. apríla 2024, 9:50 – čiže do začiatku jedenástych cvičení.
- Počas jedenástych cvičení – čiže v utorok 30. apríla 2024 od 9:50 do 11:20 – bude prebiehať štvrtý test zameraný na látku z prvých desiatich týždňov. Body z testu bude možné získať iba v prípade prítomnosti na cvičeniach v miestnosti I-H6.
Lambda výrazy
Príklad: komparátor ako lambda výraz
Pripomeňme si z piatej prednášky zostupné triedenie zoznamu celých čísel pomocou metódy Collections.sort, ktorá ako druhý parameter berie komparátor, čiže inštanciu nejakej triedy implementujúcej rozhranie Comparator<? super Integer>. Videli sme pritom tri možné spôsoby, ako komparátor definovať:
- Vytvoriť „bežnú” triedu implementujúcu rozhranie Comparator<Integer> a metódu sort zavolať pre novovytvorenú inštanciu tejto triedy.
- To isté s použitím lokálnej triedy:
import java.util.*; public class Trieda { public static void main(String[] args) { class DualComparator implements Comparator<Integer> { @Override public int compare(Integer o1, Integer o2) { return o2.compareTo(o1); } } List<Integer> a = new ArrayList<>(); a.add(6); a.add(1); a.add(3); a.add(2); a.add(3); Collections.sort(a, new DualComparator()); System.out.println(a); } }
- Definíciu triedy a vytvorenie jej inštancie spojiť do jediného príkazu s využitím mechanizmu anonymných tried:
import java.util.*; public class Trieda { public static void main(String[] args) { List<Integer> a = new ArrayList<>(); a.add(6); a.add(1); a.add(3); a.add(2); a.add(3); Collections.sort(a, new Comparator<>() { @Override public int compare(Integer o1, Integer o2) { return o2.compareTo(o1); } }); System.out.println(a); } }
Aj posledný z uvedených spôsobov definície komparátora je však stále trochu ťažkopádny – jediná pre účely triedenia podstatná informácia je tu daná riadkom return o2.compareTo(o1);, pričom zvyšok konštrukcie by bol rovnaký aj pri definícii ľubovoľného iného komparátora.
Rozhranie Comparator<E> je príkladom takzvaného funkcionálneho rozhrania – čiže rozhrania, ktoré deklaruje jedinú abstraktnú metódu; v tomto prípade ide o metódu compare. Toto rozhranie síce definuje niekoľko ďalších statických metód a metód s modifikátorom default; jedinou metódou, ktorú je potrebné implementovať kedykoľvek implementujeme rozhranie Comparator<E>, je ale metóda compare. To okrem iného znamená, že proces vytvárania tried implementujúcich rozhranie Comparator<E> sa často môže redukovať iba na implementáciu tejto jednej metódy.
Ako skrátený zápis pre definíciu inštancie anonymnej triedy implementujúcej funkcionálne rozhranie, obsahujúcej iba definíciu jedinej abstraktnej metódy deklarovanej v tomto rozhraní, slúžia v Jave takzvané lambda výrazy. Utriedenie poľa tak možno realizovať pomocou príkazu
Collections.sort(a, (o1, o2) -> {return o2.compareTo(o1);});
alebo pomocou ešte kratšieho príkazu
Collections.sort(a, (o1, o2) -> o2.compareTo(o1));
V zátvorkách pred šípkou sú postupne uvedené identifikátory argumentov metódy compare a za šípkou nasleduje blok obsahujúci telo metódy compare, prípadne výraz udávajúci výstupnú hodnotu tejto metódy.
Poznámka: pripomeňme si tiež z piatej prednášky, že uvedený príklad je čisto ilustračný – rovnako sa správajúci komparátor možno získať aj volaním statickej generickej metódy Comparator.<Integer>reverseOrder() resp. skrátene Comparator.reverseOrder().
Syntax lambda výrazov v Jave
Lambda výraz je teda skráteným zápisom anonymnej triedy implementujúcej funkcionálne rozhranie, ako aj jej inštancie. Alternatívne ich možno považovať aj za reprezentáciu implementácie abstraktnej metódy v tomto rozhraní deklarovanej; preto sa v súvislosti s lambda výrazmi často hovorí aj o anonymných funkciách. Pomenovanie „lambda výraz” odkazuje na lambda kalkul – formalizmus, v ktorom sa podobným spôsobom definujú matematické funkcie (napríklad λx.x2 + 5 je funkcia f daná predpisom f: x ↦ x2 + 5) a ktorý je okrem iného aj teoretickým základom funkcionálneho programovania.
V prípade, že má jediná abstraktná metóda deklarovaná v implementovanom funkcionálnom rozhraní hlavičku
T f(T1 arg1, T2 arg2, ..., Tn argn);
môže byť lambda výraz reprezentujúci inštanciu anonymnej triedy implementujúcej toto rozhranie tvaru
(arg1, arg2, ..., argn) -> { /* Telo implementacie metody f */ }
Pred argumentmi možno nepovinne uvádzať aj ich typy (buď sa typ uvedie pri všetkých argumentoch, alebo sa neuvedie pri žiadnom argumente). V prípade, že je počet argumentov metódy f nulový, píšeme
() -> { /* Telo implementacie metody f */ }
a v prípade, že ide o metódu s jediným argumentom arg, možno vynechať zátvorky okolo argumentov a písať iba
arg -> { /* Telo implementacie metody f */ }
V prípade, že návratový typ T metódy f nie je void, možno na pravej strane lambda výrazu namiesto bloku obsahujúceho telo implementovanej metódy uviesť iba výraz, ktorý sa vyhodnotí na typ T; v takom prípade bude metóda f počítať tento výraz. V prípade, že návratovým typom metódy f je void, možno namiesto bloku s telom implementovanej metódy uviesť volanie jednej metódy s návratovým typom void (ktoré možno z určitého pohľadu chápať ako „výraz typu void”), prípadne volanie jednej metódy s iným návratovým typom, pričom sa však návratová hodnota tejto metódy odignoruje.
Anotácia @FunctionalInterface používaná v nasledujúcich príkladoch je nepovinná (všetko by rovnako dobre fungovalo aj bez nej), ale odporúčaná – kompilátor totiž vyhodí chybu kedykoľvek je táto anotácia použitá inde ako pri funkcionálnom rozhraní.
Príklad 1:
@FunctionalInterface
public interface MyFunctionalInterface {
int f(int a, int b);
}
public class Trieda {
public static void main(String[] args) {
MyFunctionalInterface instance1 = (a, b) -> {
if (a < b) {
return 0;
}
return a + b;
};
MyFunctionalInterface instance2 = (a, b) -> a * b;
System.out.println(instance1.f(2, 3));
System.out.println(instance2.f(2, 3));
}
}
Príklad 2:
@FunctionalInterface
public interface MyFunctionalInterface {
int f(int a);
}
public class Trieda {
public static void main(String[] args) {
MyFunctionalInterface instance1 = (a) -> {return a + 1;};
MyFunctionalInterface instance2 = a -> {return a - 1;};
MyFunctionalInterface instance3 = x -> x + 3;
System.out.println(instance1.f(2));
System.out.println(instance2.f(2));
System.out.println(instance3.f(2));
}
}
Príklad 3:
@FunctionalInterface
public interface MyFunctionalInterface {
void f();
}
public class Trieda {
public static void main(String[] args) {
MyFunctionalInterface instance1 = () -> {System.out.println("Hello, lambda!");};
MyFunctionalInterface instance2 = () -> System.out.println("Hello, lambda!");
instance1.f();
instance2.f();
}
}
Rovnako ako z lokálnych a anonymných tried, možno aj z definícií lambda výrazov pristupovať k premenným inštancií, či k finálnym alebo „v podstate finálnym” lokálnym premenným metód, ktorých sú súčasťou.
Ďalší príklad použitia lambda výrazov
Rozhranie Iterable<T> deklaruje okrem iného aj metódu forEach, ktorá postupne pre všetky prvky vracané iterátorom vykoná danú akciu a predstavuje tak alternatívu k použitiu cyklu for each. Akcia, ktorá sa má pre všetky prvky vykonať, je daná inštanciou nejakej triedy implementujúcej funkcionálne rozhranie Consumer<T> z balíka java.util.function. V ňom je deklarovaná jediná abstraktná metóda accept s návratovým typom void a jediným argumentom typu T. Metóda forEach zavolá túto metódu accept postupne pre všetky prvky vracané iterátorom.
Príklad: Uvažujme napríklad nasledujúci kód postupne vypisujúci všetky prvky zoznamu a.
List<Integer> a = new ArrayList<>();
// ...
for (int x : a) {
System.out.print(x + " ");
}
Pomocou metódy forEach a anonymnej triedy môžeme rovnaké vypísanie prvkov zoznamu napísať aj nasledovne.
List<Integer> a = new ArrayList<>();
// ...
a.forEach(new Consumer<>() {
@Override
public void accept(Integer x) {
System.out.print(x + " ");
}
});
Keďže je rozhranie Consumer funkcionálne, môžeme namiesto anonymnej triedy použiť lambda výraz.
List<Integer> a = new ArrayList<>();
// ...
a.forEach(x -> System.out.print(x + " "));
Viacero ďalších funkcionálnych rozhraní, ktorých inštancie možno definovať pomocou lambda výrazov, je definovaných v balíku java.util.function.
Referencie na metódy
Občas sa stáva, že lambda výraz pozostáva iba z priameho volania nejakej už pomenovanej metódy. Pre takéto lambda výrazy existuje v Jave špeciálna syntax umožňujúca vyjadriť toto správanie o niečo stručnejším spôsobom. Lambda výrazy zadané pomocou tejto syntaxe sa nazývajú aj referenciami na metódy.
Pre triedu Trieda poskytujúcu statickú metódu statickaMetoda(T1 arg1, ..., Tn argn) a nestatickú metódu nestatickaMetoda(U1 arg1, ..., Um argm) a pre inštanciu instancia triedy Trieda možno písať:
- Trieda::statickaMetoda namiesto lambda výrazu (arg1, ..., argn) -> Trieda.statickaMetoda(arg1, ..., argn),
- instancia::nestatickaMetoda namiesto lambda výrazu (arg1, ..., argm) -> instancia.nestatickaMetoda(arg1, ..., argm),
- Trieda::nestatickaMetoda namiesto lambda výrazu (arg, arg1, ..., argm) -> arg.nestatickaMetoda(arg1, ..., argm), kde arg je inštancia typu Trieda.
Príklad: Keby sme v príklade uvedenom vyššie prvky x zoznamu a vypisovali namiesto metódy System.out.print(x + " ") metódou System.out.println(x), mohli by sme celé vypisovanie zoznamu prepísať aj nasledovne.
List<Integer> a = new ArrayList<>();
// ...
a.forEach(System.out::println);
Úvod do JavaFX
JavaFX je platforma, ktorú možno využiť na tvorbu aplikácií s grafickým používateľským rozhraním (GUI). Namiesto konzolových aplikácií teda budeme v nasledujúcich niekoľkých prednáškach vytvárať aplikácie grafické (typicky pozostávajúce z jedného alebo niekoľkých okien s ovládacími prvkami, akými sú napríklad tlačidlá, textové polia, a podobne).
- Pokyny k inštalácii JavaFX, ako aj návod na skompilovanie a spustenie prvého programu z IntelliJ IDEA a z príkazového riadku možno nájsť tu.
- Dokumentácia k JavaFX 21 API.
- Ďalšie dokumentácie a tutoriály možno nájsť na stránke projektu.
Vytvorenie aplikácie s jedným grafickým oknom
Minimalistickú JavaFX aplikáciu zobrazujúcu jedno prázdne okno o 300 krát 250 pixeloch s titulkom „Hello, World!” vytvoríme nasledovne:
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
public class HelloFX extends Application {
@Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Scene scene = new Scene(pane, 300, 250);
primaryStage.setTitle("Hello, World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Uvedený kód si teraz rozoberme:
- Hlavná trieda JavaFX aplikácie (tzn. trieda obsahujúca metódu main) sa vyznačuje tým, že dedí od abstraktnej triedy Application definovanej v balíku javafx.application, ktorý je potrebné importovať.
- Každá trieda dediaca od triedy Application musí implementovať jej abstraktnú metódu start, ktorej argumentom je objekt primaryStage typu Stage reprezentujúci hlavné grafické okno aplikácie (trieda Stage je definovaná v balíku javafx.stage, ktorý je potrebné importovať). V rámci metódy start sa vykonávajú úkony, ktoré sa majú udiať hneď po spustení aplikácie – typicky sa vytvárajú jednotlivé grafické ovládacie prvky aplikácie a špecifikujú sa ich vlastnosti.
- V našom prípade je kľúčovým riadkom metódy start volanie primaryStage.show(), ktorým zobrazíme hlavné okno aplikácie. Bez tohto volania by aplikácia bežala „na pozadí”.
- Volaním primaryStage.setTitle("Hello, World!") nastavíme titulok hlavného okna na text „Hello, World!”.
- Uvedené dva riadky často stačia na zobrazenie grafického okna s titulkom „Hello, World!” a „náhodne” zvolenou veľkosťou. V závislosti od systému sa však môže stať aj to, že sa nezobrazí nič – grafické okno totiž zatiaľ nič neobsahuje a systém nemá ako „rozumne” vypočítať jeho veľkosť; môže teda túto situáciu vyhodnotiť aj tak, že ešte nie je čo zobraziť.
- Zvyšnými riadkami už len hovoríme, že „obsahom” hlavného okna má byť prázdna oblasť o veľkosti 300 krát 250 pixelov:
- Kontajnerom pre obsah okna je inštancia triedy Scene. Ide tu o analógiu s divadelnou terminológiou: okno zodpovedá javisku; na javisku následne možno umiestniť scénu pozostávajúcu z jednotlivých rekvizít. Scénu scene možno oknu primaryStage priradiť volaním primaryStage.setScene(scene). Trieda Scene je definovaná v balíku javafx.scene, ktorý je potrebné importovať.
- Scéna je daná predovšetkým hierarchickým stromom uzlov (detaily neskôr), pričom uzlami môžu byť napríklad oblasti, ale aj ovládacie prvky ako napríklad tlačidlá, či textové polia. Volaním konštruktora Scene scene = Scene(pane, 300, 250) vytvoríme scénu o rozmeroch 300 krát 250 pixelov, ktorej koreňovým uzlom je objekt pane; ten bude v našom prípade reprezentovať prázdnu oblasť.
- Volaním konštruktora Pane pane = new Pane() vytvoríme novú oblasť pane. Tá môže neskôr slúžiť ako kontajner pre pridávanie rôznych ovládacích prvkov a podobne. Trieda Pane je definovaná v balíku javafx.scene.layout, ktorý je potrebné importovať.
- Metóda main pri jednoduchých JavaFX aplikáciách typicky pozostáva z jediného riadku, v ktorom sa volá statická metóda launch triedy Application. Tá sa postará o vytvorenie inštancie našej triedy HelloFX, o vytvorenie hlavného grafického okna aplikácie, ako aj o následné zavolanie metódy start, ktorá dostane vytvorené okno ako argument.
Okno s niekoľkými jednoduchými ovládacími prvkami
Podbne ako v príklade vyššie vytvorme aplikáciu pozostávajúcu z jediného grafického okna.
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
public class Aplikacia extends Application {
@Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Scene scene = new Scene(pane, 340, 100);
primaryStage.setTitle("Zadávanie textu");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Pridáme teraz do hlavného okna niekoľko ovládacích prvkov tak, ako na obrázku vpravo. Naším cieľom bude vytvorenie aplikácie umožňujúcej zadať text, ktorý sa pri kliknutí na tlačidlo OK zjaví v textovom popisku červenej farby. Ovládacie prvky ako textové pole alebo tlačidlo sú definované v balíku javafx.scene.control, ktorý je tak nutné importovať. Podobne na prácu s fontmi budeme potrebovať balík javafx.scene.text a na prácu s farbami balík javafx.scene.paint.
Začnime s pridaním textového popisku „Zadaj text”. Takéto textové popisky sú v JavaFX reprezentované triedou Label, pričom popisok label1 obsahujúci nami požadovaný text vytvoríme nasledovne:
import javafx.scene.control.*;
// ...
Label label1 = new Label("Zadaj text:");
Rovnako dobre by sme mohli použiť aj konštruktor bez argumentov, ktorý je ekvivalentný volaniu konštruktora s argumentom "" – text popisku label1 možno upraviť aj neskôr volaním label1.setText("Nový text").
Po jeho vytvorení ešte musíme popisok label1 pridať do našej scény – presnejšie do oblasti pane, ktorá je jej koreňovým uzlom (čo znamená, že všetky ostatné uzly budú umiestnené v tejto oblasti). Vytvorený popisok label1 teda pridáme do zoznamu synov oblasti pane nasledujúcim volaním:
pane.getChildren().add(label1);
Následne môžeme upraviť niektoré vlastnosti vytvoreného popisku, ako napríklad jeho pozíciu a font:
import javafx.scene.text.*; // Kvoli triede Font.
// ...
label1.setFont(Font.font("Tahoma", FontWeight.BOLD, 12));
label1.setLayoutX(20);
label1.setLayoutY(10);
Analogicky vytvoríme aj ostatné komponenty:
import javafx.scene.paint.*; // Kvoli triede Color.
// ...
TextField textField = new TextField();
pane.getChildren().add(textField);
textField.setFont(Font.font("Tahoma", FontWeight.BOLD, 12));
textField.setLayoutX(20);
textField.setLayoutY(30);
textField.setPrefWidth(300);
Label label2 = new Label("(Zatiaľ nebolo zadané nič)");
pane.getChildren().add(label2);
label2.setFont(Font.font("Tahoma", 12));
label2.setTextFill(Color.RED);
label2.setLayoutX(20);
label2.setLayoutY(70);
Button button = new Button("OK");
pane.getChildren().add(button);
button.setFont(Font.font("Tahoma", FontWeight.BOLD, 12));
button.setLayoutX(280);
button.setLayoutY(60);
button.setPrefWidth(40);
button.setPrefHeight(30);
Uvedený spôsob grafického návrhu scény má však hneď dva zásadné nedostatky:
- Môžeme si všimnúť, že takéto pevné rozloženie ovládacích prvkov na scéne nevyzerá dobre, keď zmeníme veľkosť okna. Provizórne môžeme tento problém vyriešiť tým, že menenie rozmerov okna jednoducho zakážeme: primaryStage.setResizable(false). Takéto riešenie má však ďaleko od ideálneho. Odporúčaným prístupom je využiť namiesto triedy Pane niektorú z jej „inteligentnejších” podtried umožňujúcich (polo)automatické škálovanie scény v závislosti od veľkosti okna. V takom prípade sa absolútne súradnice ovládacích prvkov zvyčajne vôbec nenastavujú.
- Formát jednotlivých ovládacích prvkov (ako napríklad font alebo farba) by sa po správnosti nemal nastavovať priamo v zdrojovom kóde. Namiesto toho je odporúčaným prístupom využitie štýlov definovaných v pomocných súboroch JavaFX CSS. Takto je možné meniť formátovanie bez väčších zásahov do zdrojového kódu.
Obidva tieto nedostatky napravíme v rámci nasledujúcej prednášky.
Oživenie ovládacích prvkov (spracovanie udalostí)
Dokončime našu jednoduchú aplikáciu so zadávaním textu pridaním jej kľúčovej funkcionality: po stlačení tlačidla OK (t.j. button) sa má do „červeného” popisku prekopírovať text zadaný používateľom do textového poľa.
Po stlačení tlačidla button je systémom vygenerovaná tzv. udalosť, ktorá je v tomto prípade typu ActionEvent. Udalosť je teda akýsi objekt nesúci informáciu o tom, že bolo stlačené dané tlačidlo. Každé tlačidlo – objekt typu Button – má navyše k dispozícii (zdedenú) metódu
public final void setOnAction(EventHandler<ActionEvent> value)
umožňujúcu „zaregistrovať” pre dané tlačidlo jeho spracúvateľa udalostí typu ActionEvent. Ním môže byť ľubovoľná trieda implementujúca rozhranie EventHandler<ActionEvent>, ktoré vyžaduje implementáciu jedinej metódy
void handle(ActionEvent event)
Po zaregistrovaní objektu eventHandler ako spracovávateľa udalostí ActionEvent pre tlačidlo button volaním
button.setOnAction(eventHandler);
sa po každom stlačení tlačidla button vykoná metóda eventHandler.handle.
Nami požadovanú funkcionalitu tlačidla button tak vieme vyjadriť napríklad pomocou lokálnej triedy ButtonActionEventHandler.
import javafx.event.*;
// ...
public void start(Stage primaryStage) {
// ...
class ButtonActionEventHandler implements EventHandler<ActionEvent> {
@Override
public void handle(ActionEvent event) {
label2.setText(textField.getText());
}
}
EventHandler<ActionEvent> eventHandler = new ButtonActionEventHandler();
button.setOnAction(eventHandler);
// ...
}
Skrátene môžeme to isté napísať s použitím anonymnej triedy.
public void start(Stage primaryStage) {
// ...
button.setOnAction(new EventHandler<>() {
@Override
public void handle(ActionEvent event) {
label2.setText(textField.getText());
}
});
// ...
}
Ak si navyše uvedomíme, že je rozhraní EventHandler deklarovaná jediná abstraktná metóda – ide teda o funkcionálne rozhranie – môžeme tento zápis ešte ďalej zjednodušiť použitím lambda výrazu.
public void start(Stage primaryStage) {
// ...
button.setOnAction(event -> label2.setText(textField.getText()));
// ...
}
Podrobnejšie sa spracúvaním udalostí v JavaFX budeme zaoberať na nasledujúcej prednáške.
Geometrické útvary
Špeciálnym typom uzlov, ktoré možno umiestňovať do scén, sú geometrické útvary ako napríklad Circle, Rectangle, Arc, Ellipse, Line, Polygon, atď. Všetky útvary dedia od spoločnej abstraktnej nadtriedy Shape. Sú definované v balíku javafx.scene.shape, ktorý je nutné na prácu s nimi importovať.
Aj keď útvary nevedia vyvolať udalosť typu ActionEvent, môžu vyvolávať udalosti iných typov. Napríklad kliknutie na útvar myšou vyústi v udalosť typu MouseEvent (definovanú v balíku javafx.scene.input) a spracovávateľa takejto udalosti možno pre útvar shape zaregistrovať pomocou metódy shape.setOnMouseClicked.
Nasledujúci kód vykreslí „tabuľku” o 10 krát 10 útvaroch, pričom pre každý sa náhodne určí, či pôjde o štvorec, alebo o kruh. Farba každého z útvarov sa taktiež určí náhodne. Navyše po kliknutí myšou na ktorýkoľvek z útvarov sa jeho farba náhodne zmení.
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.shape.*;
import javafx.scene.paint.*;
import java.util.*;
public class Aplikacia extends Application {
private Color randomColour(Random random) {
return Color.color(random.nextDouble(), random.nextDouble(), random.nextDouble());
}
@Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Random random = new Random();
for (int i = 0; i <= 9; i++) {
for (int j = 0; j <= 9; j++) {
Shape shape;
Color colour = randomColour(random);
if (random.nextBoolean()) {
shape = new Rectangle(j * 60 + 5, i * 60 + 5, 50, 50);
} else {
shape = new Circle(j * 60 + 30, i * 60 + 30, 25);
}
shape.setFill(colour);
shape.setOnMouseClicked(event -> shape.setFill(randomColour(random)));
pane.getChildren().add(shape);
}
}
Scene scene = new Scene(pane, 600, 600);
primaryStage.setTitle("Geometrické útvary");
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}