====== Laborator 09 ====== ====== Dezvoltarea interfețelor grafice cu utilizatorul folosind JavaFX ====== ===== Obiective ==== * familiarizarea cu facilitățile oferite de tehnologia JavaFX pentru dezvoltarea de interfețe cu utilizatorul în comparație cu alte soluții existente; * identificarea principalelor componente din cadrul arhitecturii JavaFX; * descrierea structurii generale a unei aplicații JavaFX și a firelor de execuție din contextul cărora pot fi accesate obiectele corespunzătoare controalelor grafice, respectiv interacțiunii cu utilizatorul; * implementarea unei interfețe cu utilizatorul folosind Scene Builder / FXML cu injectarea controalelor grafice / metodelor de tratare a evenimentelor în clasa controller din codul sursă; * înțelegerea mecanismului de tratare a evenimentelor legate de interacțiunea cu utilizatorul în JavaFX; dezvoltarea de filtre și metode de tratare pentru diferite acțiuni ale utilizatorului; * însușirea practicilor utilizate pentru dezvoltarea aplicațiilor JavaFX. ===== Cuvinte Cheie ==== JavaFX, FXML, Scene Builder, Quantum Toolkit, Prism, Glass, Pulse, GStreamer, WebKitHTML, CSS, layout ===== Materiale Ajutătoare ====== TODO ===== JavaFX - aspecte generale ===== JavaFX este o tehnologie pusă la dispoziţie programatorilor Java pentru a implementa sisteme informatice ce implică interacțiune cu utilizatorul, având un comportament consistent pe mai multe platforme diferite. În cadrul JavaFX a fost dezvoltat un API extins pentru aplicaţii multimedia avansate şi motoare grafice care facilitează proiectarea și dezvoltarea unor aplicaţii pentru organizaţii bazate pe date. Fiind implementat peste limbajul de programare Java, acesta va fi uşor de utilizat, optimizând costurile legate de investiţii şi complexitatea sistemelor, întrucât atât componenta server (nivelul de logică a aplicaţiei şi de acces la date) cât şi componenta client (nivelul de prezentare) vor fi dezvoltate utilizând acelaşi limbaj de programare. Câteva dintre caracteristicile JavaFX 8 sunt următoarele: * **integrare cu Java**. JavaFX este o interfaţă de programare integrată în distribuţia limbajului de programare Java, instalarea sa făcându-se împreună cu acesta; prin urmare, din JavaFX pot fi accesate toate API-urile Java cu funcţionalităţile lor, acesta reprezentând o alternativă la celelalte limbaje de programare care folosesc Java Virtual Machine, precum JRuby sau Scala; * **limbajul de adnotare FXML şi utilitarul SceneBuilder**. FXML este un limbaj declarativ de adnotare bazat pe XML prin intermediul căruia pot fi dezvoltate interfeţe grafice cu utilizatorul, fără a fi necesar ca aplicaţia să fie recompilată de fiecare dată când sunt modificate elemente din cadrul acesteia. În acest mod se realizează o separare între nivelul de prezentare și nivelul de logică a aplicației. SceneBuilder permite construirea interfeţei în mod vizual, generând automat şi documentul FXML asociat, acesta putând fi integrat apoi în orice mediu de dezvoltare. Astfel, nu mai este necesară decât implementarea mecanismelor de tratare a evenimentelor corespunzătoare diferitelor controale (elemente din cadrul interfeței grafice); * **componente WebView**. Un modul ce foloseşte tehnologia WebKitHTML permite integrarea de pagini Internet în cadrul unei interfeţe grafice. De asemenea, codul JavaScript ce rulează în [[https://docs.oracle.com/javase/8/javafx/api/javafx/scene/web/WebView.html|WebView]] poate accesa orice API-uri Java, la fel cum API-urile Java pot executa cod JavaScript care rulează în WebView. Există suport și pentru anumite funcționalități HTML5 (Web Sockets, Web Workers, Web Fonts) sau capabilități de tipărire; * **interoperabilitate cu tehnologia Swing**. Aplicaţiile Swing existente pot fi extinse cu funcţionalităţi JavaFX, ca redarea de conţinuturi grafice sau a unor pagini Internet integrate. Clasa [[https://docs.oracle.com/javase/8/javafx/api/javafx/embed/swing/SwingNode.html|SwingNode]] permite integrarea unui conținut dezvoltat folosind tehnologia Swing în cadrul aplicației JavaFX; * **biblioteci de controale şi foi de stil**. JavaFX pune la dispoziţia programatorilor majoritatea controalelor necesare pentru dezvoltarea unei interfeţe cu utilizatorul complete (inclusiv [[https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/DatePicker.html|DatePicker]] și [[https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/TreeTableView.html|TreeTableView]]), aspectul acestora putând fi particularizat folosind tehnologii web standard ca foile de stil (//eng.// CSS – cascading style sheets), prin intermediul claselor ''Styleable*''; * **tema Modena** este folosită ca implicită pentru toate aplicațiile JavaFX (înlocuind tema Caspian, care poate fi utilizată prin apelul metodei ''setUserAgentStylesheet(STYLESHEET_CASPIAN)'' în metoda ''start()'' a clasei derivate din ''Application''; * **funcționalități de grafică 3D**. Biblioteca de grafică 3D a fost îmbogățită prin: * implementarea claselor: * ''javafx.scene.shape.Shape3D'' (cu subclasele ''Box'', ''Cylinder'', ''MeshView'', ''Sphere'') * ''javafx.scene.SubScene'' * ''javafx.scene.paint.Material'' * ''iavafx.scene.input.PickResult'' * ''javafx.scene.LightBase'' (cu subclasele ''AmbientLight'' și ''PointLight'') * ''javafx.scene.SceneAntiAliasing'' * îmbunătățirea API-ului ''Camera''; * **API-ul Canvas** permite desenarea pe o suprafaţă dintr-o scenă JavaFX conţinând un element grafic (de tip nod); * **API-ul pentru tipărire** este conținut în pachetul ''javafx.print'', oferind posibilitatea configurării modului în care vor fi tipărite scenele dintr-o interfață grafică sau diferite documente; * **suport pentru text stilizat**. Controalele JavaFX pot reda informații de tip text în mai multe moduri: bidirecțional, multilinie, folosind mai multe stiluri grafice, incluzând seturi de caractere complexe; * **suport pentru operaţii multi-touch**. JavaFX poate procesa mai multe operații de tip atingere a ecranului concomitente în cazul dispozitivelor care suportă astfel de comenzi; * **suport pentru dispozitive de afișare cu rezoluție mare (HiDPI - //eng.// High Dots per Inch)**; * **grafică accelerată hardware la nivel de bandă de asamblare**. JavaFX dispune de un motor pentru redarea conţinutului grafic denumit Prism care oferă performanţe deosebite atunci când este utilizat împreună cu anumite plăci grafice sau unităţi de procesare grafică (//eng.// GPU – Graphic Processing Unit). În cazul în care un sistem nu dispune de una dintre dispozitivele recomandate pentru a rula împreună cu JavaFX, Prism va folosi în mod implicit stiva de aplicaţii Java2D; * **motor multimedia performant**, pentru redarea de conţinut multimedia folosind tehnologia GStreamer, caracterizată prin latenţă scăzută şi stabilitate; * **model de dezvoltare al aplicaţiilor autonome**. Există pachete de aplicaţii independente care dispun de toate resursele necesare precum şi de copii ale mediilor de rulare Java şi JavaFX distribuite ca pachete native ce pot fi instalate oferind utilizatorilor aceeaşi experienţă ca şi celelalte aplicaţii native sistemului de operare respectiv. JavaFX este folosit pentru aplicaţii care folosesc reţeaua de calculatoare, fiind capabile să ruleze pe mai multe tipuri de platforme, în care informaţiile pot fi vizualizate folosind o interfaţă cu utilizatorul performantă care foloseşte capabilităţi audio şi video (inclusiv tehnici de grafică avansată şi animaţii). Aplicaţiile JavaFX pot fi rulate: * direct pe o anumită maşină, unde se află "instalată" (accesând fişierul .jar care conţine programul în sine) - o astfel de opţiune este preferată în cazul în care aplicaţia nu are nevoie de acces Internet sau maşina pe care rulează nu dispune de o astfel de conexiune; * prin intermediul unui navigator (//eng.// browser), fiind integrată în cadrul unei pagini Internet, interacţiunea cu elementele paginii Internet putând fi realizată prin intermediul limbajului JavaScript; * prin descărcare de la o anumită locaţie (dacă aplicația JavaFX este găzduită pe un server web), rulând apoi pe maşina respectivă (modul WebStart); * ca program autonom, folosind copii private ale mediilor de rulare Java şi JavaFX. ===== Arhitectura JavaFX ===== Platforma JavaFX dispune de o arhitectură bazată pe o stivă de componente (transparentă pentru programator), constituind motorul ce rulează codul propriu-zis (Quantum Toolkit). Acesta cuprinde un motor grafic performant (Prism), un eficient sistem de ferestre, de dimensiuni mici (Glass), un motor pentru redarea conţinutului multimedia şi un motor pentru integrarea conţinutului Internet. {{ :laboratoare:laborator09:01javafxarchitecture.png?nolink }} ==== Setul de API-uri Java pentru funcționalități JavaFX ==== Platforma JavaFX permite accesul la setul de API-uri Java pentru a dezvolta aplicaţii ce implică interacțiunea cu utilizatorul într-un mod flexibil, exploatând capabilităţi ale acestui limbaj de programare de nivel înalt, precum: * utilizarea unor funcționalități Java, cum ar fi: clase generice, adnotări, lucrul cu mai multe fire de execuție, expresii Lambda; * accesul la JavaFX pentru dezvoltatorii de aplicații Internet din cadrul altor limbaje dinamice bazate pe mașina virtuală Java (Groovy, JavaScript); * accesul la alte limbaje dinamice bazate pe mașina virtuală Java pentru implementarea de aplicații JavaFX complexe; * folosirea unei biblioteci de asocieri ce include suport pentru rezolvarea leneșă a referințelor, legarea expresiilor și secvențelor de expresii, reevaluarea parțială a asocierilor (aceasta putând fi utilizată și din alte limbaje dinamice bazate pe mașina virtuală Java, printr-o sintaxă similară); * extinderea bibliotecilor de colecții Java cu liste sau asocieri observabile, care permit aplicațiilor să realizeze conexiuni între interfețe și modelele de date, permițând actualizarea interfeței grafice conform cu modificările detectate în cadrul modelelor de date. API-urile JavaFX au fost portate direct în Java, bazându-se mai mult pe standarde Internet, cum ar fi CSS pentru stilizarea controalelor sau ARIA pentru specificări referitoare la accesibilitate. ==== Graful de Scene ==== Implementarea unei aplicații JavaFX implică proiectarea și dezvoltarea unui graf de scene (//eng.// Scene Graph), structură ierarhică de noduri ce conţine elementele vizuale ale interfeţei grafice cu utilizatorul, care poate trata diferite evenimente legate de acestea şi care poate fi redată. Un element din graful de scene (= un nod) este identificat în mod unic, fiind caracterizat printr-o clasă de stil şi un volum care îl delimitează. Fiecare nod are un părinte (cu excepția nodului rădăcină), putând avea nici unul, unul sau mai mulţi copii. De asemenea, pentru un astfel de element pot fi definite efecte (estompări sau umbre), opacitate, transformări, mecanisme de tratare a diferitelor evenimente (care vizează interacţiunea cu utilizatorul) precum şi starea aplicaţiei. Spre diferenţă de Swing sau AWT (Abstract Window Toolkit), JavaFX conţine pe lângă mecanisme de dispunere a conţinutului, controale, imagini sau obiecte multimedia şi primitive pentru elemente grafice (ca fi texte sau figuri geometice cu care se pot crea animaţii, folosind metodele puse la dispoziţie de API-urile ''javafx.animation''). API-ul ''javafx.scene'' permite construirea următoarelor conţinuturi: * **noduri**: forme 2D şi 3D, imagini, conţinut multimedia şi conţinut Internet, text, controale pentru interacţiunea cu utilizatorul, grafice, containere; * **stări**: transformări (poziţionări şi orientări ale nodurilor), efecte vizuale; * **efecte**: obiecte care modifică aspectul nodurilor (mecanisme de estompare, umbre, reglarea culorilor). === Mecanisme de Dispunere a Conţinutului === Controalele din graful de scene pot fi grupate în containere sau panouri în mod flexibil, folosind mai multe mecanisme de dispunere a conținutului (//eng.// layout). API-ul JavaFX defineşte mai multe clase de tip container pentru dispunerea elementelor, în pachetul ''javafx.scene.layout'': * ''BorderPane'' dispune nodurile conţinute în regiunile de sus, jos, dreapta, stânga sau centru; * ''HBox'' îşi aranjează conţinutul orizontal pe un singur rând; * ''VBox'' îşi aranjează conţinutul vertical pe o singură coloană; * ''StackPane'' utilizează o stivă de noduri afişând elementele unele peste altele, din spate către față; * ''GridPane'' permite utilizatorului să îşi definească un tabel (format din rânduri şi coloane) în care să poată fi încadrate elementele conţinute; * ''FlowPane'' dispune elementele fie orizontal, fie vertical, în funcţie de limitele specificate de programator (lungime pentru dispunere orizontală, respectiv înălţime pentru dispunere verticală); * ''TilePane'' plasează nodurile conţinute în celule de dimensiuni uniforme; * ''AnchorPane'' oferă programatorilor posibilitatea de a defini noduri ancoră (referinţă) în funcţie de colţurile de jos / sus, din stânga / dreapta sau raportat la centrul containerului sau panoului. Diferitele moduri de dispunere pot fi imbricate în cadrul unei aplicaţii JavaFX pentru a se obţine funcţionalitatea dorită. === Tipuri de Controale Grafice === Controalele pentru interfaţa cu utilizatorul disponibile prin API-ul JavaFX sunt implementate ca noduri din graful de scene. Ele pot fi particularizate folosind foile de stil. Biblioteca ''javafx.scene.control'' definește cele mai frecvent utilizate controale din cadrul aplicațiilor ce conțin interfețe grafice cu utiliatorul. === Utilizarea Foilor de Stil === JavaFX permite stilizarea interfeţei cu utilizatorul folosind foi de stil (//eng.// CSS – Cascading Style Sheets), deci fără a modifica codul sursă al aplicaţiei. Acestea pot fi aplicate asupra oricărui obiect de tip ''Node'' din graful de scene, fiind aplicate asincron în momentul rulării (astfel încât pot fi modificate și dinamic). Specificația JavaFX CSS se bazează pe versiunea 2.1 a W3C CSS, cu unele actualizări din versiunea 3. Astfel, fişierele conţinând foi de stil pot fi parsate de orice utilitar specializat, chiar dacă acesta nu este integrat cu JavaFX. În acest fel, stilurile CSS pentru JavaFX și pentru alte scopuri (pagini HTML) pot fi incluse într-o unică foaie de stil. Toate proprietăţile JavaFX sunt prefixate cu şirul ''-fx-'' chiar şi pentru cele compatibile CSS HTML, datorită faptului că semantica acestora poate fi diferită pentru valorile corespunzătoare unor atribute. === Operații asupra Elementelor Grafice === == Transformări 2D/3D == Fiecare nod din graful de scene JavaFX poate suferi transformări (2D/3D) în sistemul de coordonate Oxyz folosind metodele puse la dispoziţie de pachetul ''javafx.scene.transform'': * ''translate'' – mută un nod dintr-un loc în altul de-a lungul planurilor x, y, z relativ la poziţia sa iniţială; * ''scale'' – redimensionează un nod pentru a apărea fie mai mare, fie mai mic în planurile x, y, z (în funcţie de factorul de scalare); * ''shear'' – roteşte o axă astfel încât axele x sau y să nu mai fie perpendiculare coordonatele nodului fiind modificate conform specificaţiilor; * ''rotate'' – roteşte un nod în jurul unui punct, denumit pivot al scenei; * ''affine'' – realizează o mapare (liniară) dintr-un sistem de coordonate 2D/3D în alt sistem de coordonate 2D/3D păstrând caracteristici ale dreptelor precum paralelismul sau ortogonalitatea - această clasă ar trebui să fie utilizată împreună cu clasele ''Translate'', ''Scale'', ''Rotate'' sau ''Shear'' în loc de a fi utilizată direct. == Efecte Vizuale == Îmbogăţirea aplicaţiilor JavaFX (în special a celor de timp real) se face prin efecte vizuale care operează la nivel de pixel al imaginii, toate nodurile din graful de scene fiind randate ca atare. Efectele implementate în cadrul pachetului ''javafx.scene.effect'' sunt: * ''Bloom'' (face ca zona cea mai luminoasă a unei imagini să pară strălucitoare); * ''Blur'' (adaugă un efect de neclaritate asupra obiectului, putând fi de tipul ''BoxBlur'', ''MotionBlur'' sau ''GaussianBlur''); * ''DropShadow'' / ''InnerShadow'' (adaugă o umbră în spatele / în interiorul conţinutului respectiv); * ''Reflection'' (creează o versiune reflectată a conţinutului respectiv, dispunând-o sub acesta); * ''Lighting'' (simulează o sursă de lumină care operează asupra unui continut, dând un aspect mai realistic, 3D, asupra unui obiect plat) * ''Perspective'' (mapează un dreptunghi peste un alt dreptunghi, menţinând netezimea liniilor dar nu neapărat şi paralelismul acestora). ==== Sistemul Grafic ==== În **sistemul grafic** din JavaFX pot fi implementate grafuri de scene 2D şi 3D care sunt randate software atunci când hardware-ul nu dispune de accelerare grafică sau aceasta este insuficientă. Platforma JavaFX implementează două benzi de asamblare pentru accelerare grafică: * **Prism** este folosit pentru operaţii de redare / rasterizare hardware / software a conţinutului (scene JavaFX, 2D sau 3D) folosind DirectX 9 (Windows XP / Vista), DirectX 11 (Windows 7), OpenGL (Mac, Linux, sisteme încorporate); Atunci când nu poate fi folosită accelerarea grafică la nivel hardware, se recurge la accelerarea grafică la nivel software folosind Java2D datorită răspândirii ei la nivelul JRE, mai ales când scena conţine obiecte 3D. Totuşi, trebuie să se țină cont de faptul că, în acest caz, performanţele sunt mai reduse. * **Quantum Toolkit** reprezintă un element de interfaţă între Prism şi sistemul de ferestre Glass pe care le face disponibile nivelului JavaFX, ocupându-se şi de prioritatea firelor de execuţie ce tratează redarea faţă de tratarea evenimentelor ce țin de interacțiunea cu utilizatorul. La baza stivei de componente a sistemului grafic JavaFX se află **Glass – un sistem de ferestre**, o interfaţă între API şi sistemul de operare responsabil cu gestiunea ferestrelor, mecanismelor de cronometrare, suprafeţelor, cozii ce conține evenimentele de intrare / ieşire. Spre diferenţă de alte biblioteci grafice care folosesc de regulă o coadă de evenimente de intrare/ieşire proprie, JavaFX utilizează coada de evenimente a sistemului de operare gazdă pentru a gestiona modul de alocare al acestora la firele de execuţie. O altă caracteristică proprie este rularea sistemului de ferestre Glass pe acelaşi fir de execuţie ca şi aplicaţia JavaFX (alte sisteme grafice implementează un fir de execuţie specific bibliotecii şi un fir de execuţie specific Java, ceea ce putea genera o serie de probleme). Platforma JavaFX rulează pe mai multe fire de execuţie la un moment dat (minim 2): * firul de execuţie JavaFX utilizat pentru a gestiona scenele care fac parte din ferestrele care sunt afişate; un graf de scene poate fi creat într-un fir de execuţie separat, însă atunci când nodul său rădăcină este legat de un obiect care este afișat în mod curent, acesta va fi tratat de firul de execuţie JavaFX; astfel, se permite ca utilizatorii să creeze în fundal scene complexe, menţinându-se constantă viteza de redare a scenei curente; totodată, firul de execuţie al aplicaţiei JavaFX este direrit de firul de execuţie asociat evenimentelor Swing sau AWT (//eng.// EDT - Event Dispatch Thread), astfel încât atunci când se integrează astfel de cod în aplicaţiile JavaFX, gestiunea obiectelor create în fire de execuţie diferite trebuie să se realizeze cu grijă; * firul de execuţie Prism folosit pentru redare, separat de gestiunea evenimentelor de interacțiune cu utilizatorul – permiţându-se ca un cadru să fie afişat în timp ce alt cadru este procesat în fundal; de asemenea, poate asocia alte fire de execuţie folosite pentru rasterizare, eliminând încărcarea diun procesul de randare propriu-zis; procesarea concurentă poate fi exploatată în special în contextul mașinilor care dispun de mai multe procesoare, în care fiecare fir de execuție este asociat unui alt procesor; * un fir de execuţie pentru conţinut multimedia, ce rulează în fundal, utilizat pentru sincronizarea celor mai recente cadre din graful de scene utilizând firul de execuţie JavaFX. Sincronizarea se face prin intermediul **mecanismului Pulse**. Un puls este un eveniment care indică grafului de scene JavaFX faptul că trebuie sincronizată starea elementelor pe care le conţine conform valorilor din firul de execuție Prism. El se declanşează la cel mult 60 de cadre pe secundă sau oricând sunt redate diferite animaţii / starea unui control din graful de scene trebuie modificată. Se permite astfel ca evenimentele să fie tratate asincron, în funcţie de puls. Actualizarea dispunerii conţinutului sau a stilului sunt legate de asemenea de puls. Dacă aceste modificări sunt numeroase (în cazul modificărilor din graful de scene), există riscul ca performanţa să fie redusă semnificativ, motiv pentru care ele sunt parcurse doar o singură dată în cadrul unui puls. Programatorii pot forţa parcurgerea cozii de modificări (dispunere conţinut / stil) pentru a deveni vizibile înaintea unui puls. Evenimentele din cadrul unui puls sunt executate de sistemul de ferestre Glass prin intermediul unui sistem de cronometrare (de rezoluţie înaltă) nativ. ==== Sistemul Multimedia ==== JavaFX oferă suport pentru multimedia prin API-urile ''javafx.scene.media'' care implementează atât conţinut auditiv (formatele ''.mp3'', ''.aiff'', ''.wav'') cât şi vizual (formatul ''.flv''). Sunt implementate trei componente: * obiectul ''Media'' reprezintă fişierul multimedia, auditiv sau vizual; * obiectul ''MediaPlayer'' redă conţinutul; * obiectul ''MediaView'' reprezintă nodul din graful de scene ce afişează utilizatorilor informații despre conținutul redat, oferindu-le posibilitatea de a interacționa cu acesta prin diferite operații. Motorul multimedia a fost proiectat pentru a oferi stabilitate, performanţă şi comportament consistent pentru toate platformele. ==== Sistemul pentru Redarea de Pagini Internet ==== JavaFX include şi un navigator care permite redarea conţinutului Internet prin intermediul API-ului său. Componenta Web Engine se bazează pe WebKit, un navigator open-source care suportă HTML5, CSS, JavaScript, DOM şi SVG, oferind o funcţionalitate asemănătoare cu cele mai multe produse de acelaşi tip (redare conţinut HTML local şi aflat într-o locaţie la distanţă, implementare istoric cu navigare înainte şi înapoi în cadrul acestuia, reîncărcarea conţinutului, aplicarea unor efecte, editarea conţinutului HTML, execuţia de comenzi JavaScript şi tratarea evenimentelor). La nivelul componentei care integrează navigatorul sunt definite două clase: * ''WebEngine'' care oferă funcţionalităţi de bază caracteristice unui navigator * ''WebView'' (derivat din ''Node'') care conține un obiect ''WebEngine'', încorporând conţinut HTML în scena aplicaţiei, oferind atribute şi metode pentru aplicarea de efecte şi transformări. De asemenea, apelurile Java pot fi controlate prin intermediul JavaScript şi invers, astfel încât utilizatorii pot beneficia de avantajele ambelor medii de programare. ===== Structura Generală a unei Aplicații JavaFX ===== O aplicaţie JavaFX trebuie să aibă o clasă principală, derivată din ''javafx.application.Application''. Metoda ''start()'' a acesteia trebuie să fie suprascrisă, primind ca unic parametru un obiect de tip ''javafx.stage.Stage'', fereastra principală a aplicației, pentru care se indică titlul, scena și vizibilitatea. Acestă metodă este apelată în mod automat la execuţia clasei principale. Containerul interfeţei cu utilizatorul se exprimă prin clase de tip: * ''javafx.stage.Stage'' - fereastra principală; * ''javafx.scene.Scene'' - are asociat graful de noduri menţinând un model intern al elementelor grafice din cadrul aplicaţiei; la fiecare moment de timp, acesta ştie ce obiecte să afişeze, care zone ale ecranului trebuie actualizate şi cum să realizeze acest proces într-un mod eficient; metodele de desenare nu sunt apelate manual de utilizator, ci automat de sistemul de randare, conform informaţiilor reţinute în graful de noduri. {{ :laboratoare:laborator09:02applicationstructure.png?nolink&500 }} Fiecare element din cadrul unei scene este exprimat sub forma unui nod dintr-un graf (arbore) în care elementul rădăcină este reprezentat de containerul din care acestea fac parte. Pachetul ''javafx.scene'' defineşte clase aferente celor trei tipuri de noduri care pot apărea în graful (arborele) de scene: * ''Scene'': containerul de bază pentru întregul conţinut (nod rădăcină); * ''Parent'': clasă abstractă pentru nodurile ce pot avea copii (nod ramură); implementări ale acestei clase sunt ''Control'', ''Group'', ''Region'' şi ''WebView''; * ''Node'': clasă abstractă pentru toate elementele grafice, folosită în special pentru nodurile care nu pot avea copii (nod frunză); clasele ce pot fi folosite în acest scop sunt definite mai cu seamă în pachetele ''javafx.scene.shape'' şi ''javafx.scene.text''. {{ :laboratoare:laborator09:03scenegraph.png?nolink&500 }} Aceste clase de bază definesc funcţionalităţi importante care vor fi moştenite de subclasele lor, inclusiv ordinea de desenare, vizibilitatea, compunerea transformărilor sau suportul pentru stiluri CSS. ==== Exemplu ==== Se implementează o aplicaţie JavaFX denumită ''BookStore'' ce extinde clasa ''javafx.application.Application''. Metoda ''start()'' primeşte un argument de tip ''javafx.stage.Stage'' ce reprezintă container-ul de nivel înalt (fereastra vizibilă a aplicației). Această metodă este apelată în mod automat în momentul în care se creează o instanță a clasei. import javafx.application.Application; import javafx.stage.Stage; import ro.pub.cs.aipi.lab09.controller.Authentication; public class BookStore extends Application { @Override public void start(Stage mainStage) throws Exception { new Authentication().start(); } public static void main(String[] args) { launch(args); } } La rândul său, aceasta creează o instanță a clasei ''Authentication'', pentru care apelează metoda ''start()''. Acesta obține un obiect ''Scene'' pe baza încărcării conținutului unui fișier ''.fxml'' pe care îl afișează în cadrul unui obiect ''Stage'' (printr-un apel al metodei ''show()''), după ce a indicat alte proprietăți ale acestuia (titlu, pictogramă). Specificarea unei scene ca parte a unei ferestre se face printr-un apel al metodei ''setScene()''. import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Node; import javafx.scene.Parent; import javafx.stage.Stage; import javafx.scene.Scene; import ro.pub.cs.aipi.lab09.general.Constants; public class Authentication { private Stage applicationStage; private Scene applicationScene; // ... public void start() { try { applicationScene = new Scene((Parent)FXMLLoader.load(getClass().getResource(Constants.AUTHENTICATION_FXML))); } catch (Exception exception) { System.out.println ("An exception has occured: "+exception.getMessage()); if (Constants.DEBUG) exception.printStackTrace(); } applicationStage = new Stage(); applicationStage.setTitle(Constants.APPLICATION_NAME); applicationStage.getIcons().add(new Image(getClass().getResource(Constants.ICON_FILE_NAME).toExternalForm())); applicationStage.setScene(applicationScene); applicationStage.show(); } // ... } ===== ===== Metoda ''main()'' nu este necesară atunci când fişierul ''.jar'' corespunzător aplicaţiei JavaFX este creat folosind utilitarul JavaFX Packager, care include şi un mecanism pentru lansarea aplicaţiei în cadrul acesteia (JavaFX launcher). Totuşi, metoda ''main()'' va trebui implementată atunci când nu se utilizează acest utilitar (IDE-uri care nu sunt integrate complet cu JavaFX). În această situaţie, din metoda ''main()'' va trebui apelată metoda ''Application.launch()''. ===== Componente ale Interfeței cu Utilizatorul ===== ==== Controale Grafice ==== Controalele care gestionează interfaţa cu utilizatorul în JavaFX sunt noduri în graful de scene, putând fi particularizate fie folosind foi de stil CSS, fie utilizând clasa ''Skin''. Totodată, ele pot fi integrate cu uşurinţă cu alte aplicaţii grafice Java (integrarea cu o aplicaţie Swing se face creând un obiect ''Scene'' la care se adaugă toate nodurile - având un mod de dispunere asociat -, acesta fiind ulterior adăugat la containerul corespunzător din aplicaţia Swing respectivă; chiar şi în acest caz, elementele JavaFX sunt randate cu Prism). Clasele care implementează controale din JavaFX se găsesc în pachetul ''javafx.scene.control''. Fiecare control dispune de atribute şi metode (suplimentare faţă de cele din clasa ''Control'') pentru a oferi suport complet în interacţiunea cu utilizatorul. Clasa ''Control'' poate fi extinsă definindu-se un obiect care corespunde nevoilor utilizatorilor. {{ :laboratoare:laborator09:04javafxgraphicelements.png?nolink&700 }} Pe lângă controalele clasice (existente în AWT şi Swing) au fost dezvoltate şi alte componente, precum ''Accordion'', ''ColorPicker'', ''Chart'', ''DatePicker'', ''Pagination'', ''TitledPane'', ''TreeTableView''. Fiind derivate din clasa ''Node'', controalele pentru interfaţa cu utilizatorul pot fi integrate în diferite animaţii, efecte, transformări sau tranziţii în cadrul procesului de randare a scenei. === Label === Un obiect ''Label'' reprezintă o etichetă (non-editabilă) ce poate conține în cadrul său un text (obiect de tip ''String'') și / sau o imagine (obiect de tip ''ImageView''). Acestea pot fi specificate ca parametri fie în cadrul constructorului, fie în cadrul metodelor de tip setter specifice: * public void setText(String text) * public void setGraphic(Node graphic) Alinierea textului în cadrul etichetei utilizează metoda ''setTextAlignment()'', iar definirea unei poziţii a elementului grafic faţă de text se face prin metoda ''setContentDisplay()'' care poate primi ca argument una din constantele clasei ''ContentDisplay'' şi anume: ''LEFT'', ''RIGHT'', ''CENTER'', ''TOP'', ''BOTTOM''. În situaţia în care textul trebuie împărţit între mai multe rânduri, metoda ''setWrapText()'' va primi ca argument valoarea ''true''. Totodată, în cazul în care textul asociat nu poate fi vizualizat datorită dimensiunilor etichetei respective, se foloseşte metoda ''setTextOverrun()'' care primeşte ca argument unul dintre tipurile clasei ''OverrunStyle'' ce defineşte modul în care va fi procesat textul care nu a putut fi vizualizat corespunzător. Textul poate fi redat folosind un anumit set de caractere, indicat ca parametru al metodei ''setFont()'' și o anumită culoare, specificată ca parametru al metodei ''setTextFill()''. Dacă se foloseşte atât text cât şi imagine pentru o etichetă, distanţarea dintre ele se poate face folosind metoda ''setGraphicTextGap()''. Efectele care pot fi aplicate asupra obiectelor de tip ''Labeled'', ca de altfel asupra tuturor controalelor din JavaFX sunt rotirea (''setRotate()''), translaţia (''setTranslate()'') şi mărirea/micşorarea (''setScale()''), numele metodelor putând fi sufixate de axa ce serverşte drept referinţă a operaţiei respective. === Button === Un obiect ''Button'' are funcția de a produce o acţiune în momentul în care este apăsat. Execuţia acestei operaţii se face prin transmiterea unui obiect ''EventHandler'' ca parametru al metodei ''setOnAction()''. Acesta trebuie să implementeze metoda ''handle()'' care realizează procesările asociate acţiunii de apăsare a unui buton. Cel mai frecvent, tipul de eveniment asociat este ''ActionEvent'': button.setOnAction((ActionEvent event) -> { // ... } ); Obiectul de tip ''Button'' este derivat din clasa ''Labeled'', astfel încât conținutul său poate fi atât text cât și imagine, acestea putând fi transmise în constructor sau în metodele setter respective. Asupra unui obiect de tip ''Button'' pot fi aplicate efecte (din pachetul ''javafx.scene.effect'') prin metoda ''setEffect()'' asociate de regulă evenimentelor legate de mouse, cum ar fi ''MouseEvent.MOUSE_ENTERED'' şi ''MouseEvent.MOUSE_EXITED''. Tratarea unor astfel de evenimente se face prin specificarea unei metode specifice, apelată automat în momentul producerii acestuia, asociată prin metoda ''addEventHandler()''. Aceasta primește ca parametri tipul de eveniment, precum și obiectul (de tip ''EventHandler'') care gestionează operațiile realizate în momentul producerii sale. Stilizarea unui buton se face apelând metoda ''setStyleClass()'' ce primeşte drept argument un fişier de tip ''.css'' sau prin metoda ''setStyle()'' în care elementele specifice de stil sunt precizate manual. === RadioButton === Un obiect ''RadioButton'' este un tip de buton care are proprietatea că poate fi la un moment dat de timp fie selectat, fie deselectat. De regulă, mai multe astfel de obiecte sunt grupate împreună în cadrul unui obiect de tip ''ToggleGroup'', care asigură selecția exclusivă între acestea (un singur control poate fi selectat din grup poate fi selectat). Apartenența unui buton radio la un grup este precizată prin metoda ''setToggleGroup()'' care primeşte ca argument elementul din care va face parte. Selecţia unui anumit buton din grup trebuie realizată atât prin metoda ''setSelected(true)'' cât şi prin metoda ''requestFocus()''. Verificarea faptului că butonul a fost selectat se face prin metoda ''isSelected()''. Identificarea butonului selectat în cadrul interacţiunii cu utilizatorul implică definirea, pentru grupul de butoane (de tip ''ToggleGroup''), a unui obiect ascultător de tip ''ChangeListener'' pentru care trebuie implementată metoda ''changed()'': final ToggleGroup toggleGroup = new ToggleGroup(); toggleGroup.selectedToggleProperty().addListener( (ObservableValue observableValue, Toggle oldToggle, Toggle newToggle) -> { if (toggleGroup.getSelectedToggle() != null) { Toggle selectedToggle = toggleGroup.getSelectedToggle(); // ... } } ); === ToggleButton === Un obiect ''ToggleButton'' este de asemenea un tip de buton care are proprietatea că poate fi la un moment dat de timp fie selectat, fie deselectat, mai multe elemente de acest fel putând fi grupate împreună. Totuși, într-un grup (obiect ''ToggleGroup'') format din mai multe astfel de controale, există posibilitatea ca nici un element să nu fie selectat la un moment dat de timp, păstrându-se totuși constrângerea de tip selecție exclusivă. === CheckBox === Un obiect ''CheckBox'' are proprietatea de a fi la un moment dat de timp selectat sau deselectat, însă mai multe elemente de acest tip nu pot fi grupate în obiecte ''ToggleGroup'', tocmai pentru a permite selecția simultană. Un obiect ''CheckBox'' poate fi definit (situaţie în care acesta poate fi selectat sau deselectat) sau nedefinit, caz în care starea sa nu poate fi modificată ca urmare a interacţiunii cu utilizatorul. Definirea obiectului presupune specificarea proprietăţilor sale prin metodele ''setSelected()'' respectiv ''setIndeterminate()'': * în cazul în care proprietatea ''INDETERMINATE'' are valoarea ''false'', starea unui obiect ''CheckBox'' poate fi selectat sau deselectat; * în cazul în care proprietatea ''INDETERMINATE'' are valoarea ''true'', starea unui obiect ''CheckBox'' poate fi selectat, deselectat sau nedefinit; dacă se doreşte ca obiectul ''CheckBox'' să cicleze între trei valori: selectat, neselectat şi nedefinit, se precizează proprietatea ''allowIndeterminate''. ^ ^ ''INDETERMINATE=false'' ^ ''INDETERMINATE=true'' ^ | **''SELECTED=false''** | {{ :laboratoare:laborator09:05checkbox-state1.png?nolink }} | {{ :laboratoare:laborator09:05checkbox-state2.png?nolink }} | | **''SELECTED=true''** | {{ :laboratoare:laborator09:05checkbox-state3.png?nolink }} | ::: | === ChoiceBox === Un obiect ''ChoiceBox'' se oferă posibilitatea selecţiei între un număr relativ mic de opţiuni. Elementele din cadrul unui astfel de obiect (create prin metoda ''FXCollections.observableArrayList()'') pot fi specificate fie atunci când obiectul este construit, fie prin metoda ''setItems()''. Delimitarea dintre opţiuni se poate realiza utilizând obiecte de tip ''Separator''. Totodată, nodul poate fi însoţit de o explicaţie suplimentară, folosind metoda ''setTooltip()'' (care primeşte drept parametru un obiect de tipul ''Tooltip''). Identificarea indexului pe care îl deţine elementul selectat în cadrul interacţiunii cu utilizatorul se face prin intermediul unui obiect ascultător de tipul ''ChangeListener'', pentru care se implementează metoda ''changed()'': choiceBox.getSelectionModel().selectedIndexProperty().addListener( (ObservableValue observableValue, Number oldValue, Number newValue) -> { int selectedIndex = newValue.intValue(); // ... } ); Metoda ''getSelectionModel()'' întoarce elementul selectat din cadrul obiectului ''choiceBox'', în timp ce metoda ''selectedIndexProperty()'' returnează proprietatea ''SELECTED_INDEX'' a acestuia. === TextField / PasswordField === Clasele ''TextField'' şi ''PasswordField'', derivate din clasa ''TextInput'', oferă posibilitatea de a gestiona un text de la utilizator pe care îl pot afişa. Diferenţa dintre clasele ''TextField'' şi ''PasswordField'' constă în faptul că în cazul ''TextField'' conţinutul este afişat în clar în timp ce pentru ''PasswordField'' fiecare caracter este înlocuit prin ●. O practică frecventă pentru obiecte de tip ''PasswordField'' este ştergerea conţinutului său după ce a fost realizată procesarea sa. Crearea unui astfel de obiect se face cu sau fără argument. Dimensiunea câmpului text este precizată prin metoda ''setPrefColumnCount()''. Unui astfel de element i se poate asocia un text descriptiv indicând semnificaţia conţinutului, prin metoda ''setPromptText()''. O astfel de funcționalitate este utilă atunci când nu se utilizează etichete împreună cu câmpurile text pentru a specifica conținutul acestora. Totuși, doar valoarea specificată de utilizator poate fi obținută prin metoda ''getText()'' asociată acestui tip de control. Ştergerea textului conţinut de aceste obiecte se face apelând metoda ''clear()''. Metodele puse la dispoziţie de aceste clase sunt: * ''copy()'' – transferă textul selectat într-o zonă de memorie, menţinându-l în obiectul curent; * ''cut()'' – transferă textul selectat într-o zonă de memorie, nemenţinându-l în obiectul curent; * ''selectAll()'' - selectează întregul conținut din cadrul obiectului curent; * ''paste()'' – transferă conţinutul zonei de memorie în obiectul curent, înlocuind textul selectat. Procesarea valorii din cadrul unui câmp text poate fi realizată prin intermediul unui obiect de tipul ''EventHandler'' specificat ca parametru al metodei ''setOnAction()''. === ScrollBar === Un obiect ''ScrollBar'' permite crearea de panouri şi zone de vizualizare care pot fi derulate. Elementele sale sunt cursorul, bara culisantă precum şi butoanele stâng şi drept. Metodele ''setMin()'' şi ''setMax()'' sunt utilizate pentru a exprima limitele între care se mişcă cursorul și ''setValue()'' pentru a specifica poziţia curentă a cursorului. Implicit, cursorul este centrat pe orizontală (se găsește la valoarea (min + max) / 2). Metoda ''setOrientation()'' este folosită pentru a distinge între tipurile orizontal şi vertical. Mutarea cursorului (într-o anumită direcţie) cu o singură unitate (specificată prin proprietatea ''UNIT_INCREMENT'') se face accesând butoanele din stânga sau din dreapta (respectiv sus şi jos). Atributul ''BLOCK_INCREMENT'' indică deplasarea implicită în momentul în care utilizatorul apasă bara culisantă în loc de butoanele de la extremităţile acesteia. === ScrollPane === Un obiect de tip ''ScrollPane'' permite vizualizarea elementelor grafice a căror dimensiune depășește suprafața de afișare prin controale care culisează. Conţinutul (un singur nod!!!) unui astfel de obiect este specificat prin intermediul metodei ''setContent()''. Dacă se doreşte includerea mai multor noduri, se vor folosi containere care gestionează dispunerea conţinutului sau obiecte din clasa ''Group''. Totodată, metoda ''setPannable()'' poate fi utilizată (cu argumentul ''true'') pentru ca obiectul conţinut să fie previzualizat odată cu mişcarea mouse-ului. Se pot specifica politici de afişare a elementelor derulante, atât pentru verticală cât şi pentru orizontală prin metodele ''setHbarPolicy()'' / ''setVbarPolicy()''. Parametrii pe care îi pot primi aceste metode sunt definite în clasa ''ScrollBarPolicy'': ''ALWAYS'', ''NEVER'', ''AS_NEEDED''. Componentele din cadrul unui obiect ''ScrollPane'' pot fi redimensionate pentru a corespunde spaţiului existent (metodele ''setFitToWidth()'' / ''setFitToHeight()''). Implicit, proprietăţile ''FIT_TO_WIDTH'' şi ''FIT_TO_HEIGHT'' au valoarea ''false''. De asemena, clasa ''ScrollPane'' oferă posibilitatea de a se afişa dimensiunea minimă, dimensiunea maximă precum şi dimensiunea curentă a componentelor pe care le conţine. Proprietăţile ''HVALUE'' şi ''VVALUE'' ale obiectului ''ScrollPane'' identifică poziţia pe care o are cursorul de derulare pe orizontală, respectiv pe verticală. === ListView === Obiectul ''ListView'' este utilizat pentru vizualizarea unei liste care conţine mai multe elemente, astfel încât pentru inspectarea acestora este necesară existenţa unei bare derulante. Componentele sale pot fi specificate fie atunci când obiectul este construit fie prin metodele ''setItems()'' (se foloseste constructorul ''FXCollections.observableArrayList()''), respectiv ''setCellFactory()''. Modificarea dimensiunilor implicite se face prin ''setPrefHeight()'', respectiv ''setPrefWidth()'', cu valorile exprimate în pixeli. Lista poate fi orientată pe verticală sau orizontală, în funcţie de parametrul pe care îl primeşte metoda ''setOrientation()'' (''Orientation.HORIZONTAL'' sau ''Orientation.VERTICAL''). ListView listView = newListView<>(); ObservableList entries = FXCollections.observableArrayList(...); listView.setItems(entries); listView.setPrefWidth(...); listView.setPrefHeight(...); listView.setOrientation(...); Clasele ''SelectionModel'' şi ''FocusModel'' sunt folosite pentru a identifica selecţia şi focusul din cadrul listei (aceste proprietăţi pot fi obţinute, însă nu pot fi specificate pentru a determina valorile selectate, respectiv focusate în cadrul listei): * ''getSelectionModel().getSelectedIndex()'' – întoarce indexul elementului selectat în mod curent; * ''getSelectionModel().getSelectedItem()'' – întoarce elementul selectat; * ''getFocusModel().getFocusedIndex()'' – întoarce indexul elementului care deţine focusul în mod curent; * ''getFocusModel().getFocusedItem()'' – întoarce elementul focusat. Obiectele ''ListView'' sunt create în mod implicit având ca model de selecţie o implementare a clasei abstracte ''MultipleSelectionModel'', cu proprietatea ''selectionMode'' având valoarea ''SelectionMode.SINGLE''. Pentru a permite selecţia multiplă se va folosi metoda ''setSelectionMode()'' având ca parametru ''SelectionMode.MULTIPLE'', elementele selectate obţinându-se prin proprietăţile ''selectedItems'' / ''selectedIndices''. Dacă se doreşte ca datele din listă să poată fi modificate de utilizator (implicit o astfel de operație nu este permisă), se pot utiliza extensii ale clasei ''ListCell'' ca ''CheckBoxListCell'', ''ChoiceBoxListCell'', ''ComboBoxListCell'' sau ''TextFieldListCell''. Redefinirea implementării unei celule a listei la aceste tiprui de obiecte se face prin metoda ''setCellFactory()''. === TableView === JavaFX pune la dispoziţie programatorilor şi clase pentru a reprezenta datele într-o formă tabelară: ''TableView'', ''TableColumn'' şi ''TableCell''. Popularea implică definirea unui model pentru tabel şi asocierea unei fabrici pentru celule. Modelul de date este o clasă ale cărei atribute au tipul ''SimpleStringProperty'' şi pentru care se definesc metode de tip getter şi setter. Pentru fiecare coloană existentă în cadrul tabelului se va specifica, pe lângă nume şi dimensiune şi fabrica pentru valorile pe care le va conţine: for (String attribute: attributes) { TableColumn column = new TableColumn(attribute); column.setMinWidth(tableContentTableView.getPrefWidth() / attributes.size()); column.setCellValueFactory(new PropertyValueFactory(attribute)); tableContentTableView.getColumns().add(column); } populateTableView(null); public void populateTableView(String whereClause) { try { ArrayList> values = DataBaseWrapperImplementation.getInstance().getTableContent(tableName, null, (whereClause == null || whereClause.isEmpty()) ? null : whereClause, null, null); ObservableList data = FXCollections.observableArrayList(); for (ArrayList record:values) data.add(getCurrentEntity(record)); tableContentTableView.setItems(data); } catch (SQLException sqlException) { System.out.println("An exception had occured: " + sqlException.getMessage()); if (Constants.DEBUG) sqlException.printStackTrace(); } } Metoda ''setCellValueFactory()'' specifică o fabrică de celule pentru fiecare coloană în parte. Aceasta este implementată folosind clasa ''PropertyValueFactory'' ce utilizează denumirile coloanelor tabelelor ca referinţe către metodele aferente ale clasei ''Model''. Tabelele JavaFX au capacitatea de a sorta datele. Sortarea obiectelor din cadrul unei coloane se face printr-un click al mouse-ului. În funcţie de numărul de click-uri ale mouse-ului, elementele vor fi sortate crescător (1), descrescător (2) sau deloc (3). Programatorul poate indica din cod preferinţele pentru sortarea conţinutului, folosind metoda ''setSortType()'', care primeşte ca parametru o constantă din clasa ''TableColumn.SortType'' (având valorile ''ASCENDING'' / ''DESCENDING''). Coloanele care vor fi sortate pot fi indicate prin apăsarea tastei ''Shift'' înainte de selectarea lor, fie prin metoda ''TableView.sortOrder()'' având ca parametru lista observabilă a coloanelor. Posibilitatea de realizare a sortării informațiilor din cadrul tabelelor se face prin intermediul metodei ''setSortable()''. Tabelele JavaFX oferă posibilitatea de a redimensiona coloanele în funcţie de conţinut. În cazul în care se doreşte editarea conţinutului unor tabele, se va folosi metoda ''setEditable(true)''. Structura unui tabel presupune existenţa unor coloane (obiecte de tip ''TableColumn'') care se adaugă la obiectul de tip ''TableView''. Pentru un obiect de tip ''TableColumn'' se poate utiliza metoda ''setVisible(false)'' dacă nu se doreşte vizualizarea conţinutului său. De asemenea, se pot crea structuri mai complexe, cum ar fi de exemplu coloane imbricate, adăugând la coloana părinte coloanele copil: TableColumn parent = new TableColumn("Parent"); TableColumn child1 = new TableColumn("Child1"); TableColumn child2 = new TableColumn("Child2"); parent.getColumns().addAll(child1, child2); În situaţia în care tabelul nu conţine nici un fel de date, este afişat mesajul ''"No content in table"''. Dacă se doreşte afişarea altui text, acesta poate fi precizat prin metoda ''setPlaceHolder()''. === TreeView === Un obiect ''TreeView'' permite vizualizarea de conţinuturi care respectă o structură ierarhică în care elementul cel mai de sus este numit rădăcină, iar elementele cele mai de jos sunt numite frunze. Construirea unui obiect ''TreeView'' implică definirea mai multor obiecte ''TreeItem'' între care se stabilesc relaţii de tip părinte-fiu. Obiectul ''TreeItem'' rădăcină este specificat fie ca parametru al constructorului obiectului ''TreeVioew'', fie ca parametru al metodei ''setRoot()''. Indicarea unui nod fiu se face prin transmiterea sa ca parametru al metodei ''add()'' apelată pentru lista observabilă a elementelor din nodul părinte (care se obține prin metoda ''getChildren()''). Obiectele ''TreeItem'' pot fi expandate (vizualizându-se conţinutul lor) sau nu, în funcţie de parametrul transmis metodei ''setExpanded()''. Implicit, conţinutul obiectelor ''TreeItem'' nu poate fi vizualizat. Clasa ''TreeItem'' este parametrizată (''TreeItem (T value)'') întrucât poate conţine elemente de orice tip (inclusiv alte controale). Pentru fiecare obiect de tip ''TreeItem'' poate fi asociat un element grafic ce poate fi precizat fie în constructor sau poate fi transmis ca parametru al metodei ''setGraphic()''. Întrucât clasa ''TreeItem'' nu este derivată din ''Node'', supra obiectelor de acest tip nu pot fi aplicate efecte vizuale. De aceea, trebuie definit un mecanism de tip "fabrică" pentru a genera obiecte de tip ''TreeCell'' al căror comportament poate fi modificat în funcţie de necesităţi. Această abordare este utilă mai ales când obiectul respectiv va conţine o cantitate mare de informaţii care este modificată în mod dinamic. De asemenea, acest mecanism poate fi folosit atunci când se doreşte redefinirea tipului de control conţinut, cu specificarea comportamentului aplicaţiei în cazul când utilizatorul interacţionează cu tipul de nod respectiv, putând fi folosite ''CheckBoxTreeCell'', ''ChoiceBoxTreeCell'', ''ComboBoxTreeCell'', ''TextFieldTreeCell''. Verificarea relaţiilor dintre obiectele de tip ''TreeItem'' se face prin metodele ''getParent()'', respectiv ''isLeaf()''. === TreeTableView === Un obiect ''TreeTableView'' reprezintă o combinație a funcționalităților oferite de clasele ''TableView'' și ''TreeView'', permițând vizualizarea unei ierarhii de date (nelimitate) organizate tabelar. Procesul de construire al unui obiect de tip ''TreeTableView'' implică mai multe etape: - definirea nodurilor din cadrul arborelui (obiecte de tip ''TreeItem'') final TreeItem childNode1 = new TreeItem<>(new Person("FirstName1, LastName1")); final TreeItem childNode2 = new TreeItem<>(new Person("FirstName2, LastName2")); - definirea nodului rădăcină (obiect de tip ''TreeItem'') final TreeItem rootNode = new TreeItem<>("Persons"); rootNode.setExpanded(true); - stabilirea relațiilor de tip părinte-copil între nodurile din cadrul arborelui rootNode.getChildren().setAll(childNode1, childNode2); - definirea atributelor (coloanelor) TreeTableColumn firstNameColumn = new TreeTableColumn<>("First Name"); TreeTableColumn lastNameColumn = new TreeTableColumn<>("Last Name"); - specificarea conținutului atributelor (coloanelor), prin intermediul unor fabrici de celule, ce furnizează obiecte de tip ''TreeItem'', pe baza proprietăților clasei ale căror obiecte sunt reprezentate firstNameColumn.setCellValueFactory((CellDataFeatures parameter) -> new ReadOnlyStringWrapper(parameter.getValue().getValue().getFirstName())); lastNameColumn.setCellValueFactory((CellDataFeatures parameter) -> new ReadOnlyStringWrapper(parameter.getValue().getValue().getLastName())); - construirea unui obiect de tip ''TreeTableView'' final TreeTableView treeTableView = new TreeTableView<>(rootNode); treeTableView.setShowRoot(true); - asocierea atributelor (coloanelor) la obiectul de tip ''TreeTableView'' treeTableView.getColumns().addAll(firstNameColumn, lastNameColumn); Dacă se dorește ca utilizatorii să aibă acces la vizibilitatea coloanelor, se poate apela metoda ''setTableMenuButtonVisible()'', care în funcție de valoarea parametrului furnizat, afișează (pentru ''true'') sau nu (pentru ''false'') în antetul tabelului un buton (''+'') ce oferă o astfel de funcționalitate. Vizibilitatea nodului rădăcină este controlată prin intermediul metodei ''setShowRoot()'' care primește un parametru de tip ''boolean''. Ordonarea conținutului pentru fiecare atribut în parte se poate face: * pentru fiecare coloană în parte, prin metoda ''setSortType()'', ce primește ca parametru o constantă din clasa ''TreeTableColumn.SortType'' (''ASCENDING'', ''DESCENDING''); * pentru întregul tabel, prin metoda ''setSortMode()'' * ''TreeSortMode.ALL_DESCENDANTS'' - regulile de ordonare se aplică la toți descendenții * ''TreeSortMode.ONLY_FIRST_LEVEL'' - regulile de ordonare se aplică numai pentru descendenții de pe primul nivel Un obiect ''TreeTableView'' implementează un model de selecție ''SelectionMode.SINGLE'', definit de clasa abstractă ''MultipleSelectionModel''. Pentru a permite selecția mai multor noduri ale arborelui / atribute (coloane), trebuie apelate succesiv metodele ''setSelectionMode()'', respectiv ''setCellSelectionEnabled()''. ^ **''setSelectionMode()''** ^ **''setCellSelectionEnabled()''** ^ **COMPORTAMENT** ^ | ''SelectionMode.SINGLE'' | ''false'' | permite selecția unui singur rând din tabelă | | ''SelectionMode.SINGLE'' | ''true'' | permite selecția unei singure celule din tabelă | | ''SelectionMode.MULTIPLE'' | ''false'' | permite selecția mai multor rânduri din tabelă | | ''SelectionMode.MULTIPLE'' | ''true'' | permite selecția mai multor celule din tabelă | === ComboBox === Un obiect ''ComboBox'' este folosit atunci când este necesară selectarea unei opţiuni dintr-o listă care depăşeşte o anumită limită, pentru că oferă posibilitatea vizualizării folosind elemente derulante, spre deosebire de obiectele ''ChoiceBox''. Elementele conţinute (obiect de tip listă observabilă) sunt specificate fie la construirea obiectului, fie apelând metoda ''setItems()'', fie folosind metoda ''addAll()'' aplicată unui obiect obţinut din ''getItems()''. Selecţia poate fi specificată prin metoda ''setValue()'' (care modifică şi obiectul asociat din proprietatea ''selectionModel'', chiar dacă valoarea indicată nu se regăsește printre valorile din cadrul controlului), și poate fi obţinută prin metoda ''getValue()''. Numărul de opţiuni vizibile din cadrul listei va fi modificat folosind metoda ''setVisibleRowCount()'', primind ca parametru numărul de elemente care vor fi afişate. Deși clasa ''ComboBox'' este generică, este recomandat ca aceasta să nu fie parametrizată folosind obiecte ale clasei ''Node'' sau derivate din aceasta. Întrucât structura grafului de scene implică faptul că un singur nod se poate afla într-o anumită locație la un moment dat, în momentul accesării unui control din cadrul unui obiect ''ComboBox'', el va fi eliminat din acesta, urmând să fie adăugat înapoi la modificarea selecției. În schimb, se vor folosi mecanisme de tip "fabrică" de celule (printr-un apel al metodei ''setCellFactory()'' ce primește ca parametru un obiect de tip ''Callback'' pentru care trebuie implementată metoda ''call()''). === Separator === Elementul ''Separator'' este utilizat doar pentru a distinge între elementele unui grup sau pentru a structura interfaţa grafică, fără a produce vreo acţiune. Un obiect ''Separator'' poate fi orizontal sau vertical, orientarea sa modificându-se prin metoda ''setOrientation()''. Dimensiunea unui astfel de obiect poate fi precizată în cadrul metodei ''setMaxWidth()'', în timp ce poziţia sa (orizontală sau verticală) se face cu ''setValignement()'', respectiv ''setHalignement()'', altfel implicit acesta ocupă tot spaţiul care îi este alocat. Fiind derivat din clasa ''Node'', el poate fi stilizat, i se pot adăuga efecte vizuale şi poate fi animat. === Slider === Un control ''Slider'' conţine o pistă şi un cursor care poate fi poziţionat. Totodată, poate include marcaje ce pot avea etichete asociate, indicând diferite valori numerice din intervalul respectiv. Metodele pe care le pune la dispoziţie această clasă sunt: ''setMin()'', ''setMax()'', ''setValue()'', ''setShowTickLabels()'', ''setShowTickMarks()'', ''setMajorTickUnit()'', ''setMinorTickCount()'', ''setBlockIncrement()''. Metodele ''setMin()'' şi ''setMax()'' definesc valorile extreme între care poate fi derulat obiectul de tip ''Slider'', în timp ce ''setValue()'' indică valoarea curentă a cursorului (care trebuie să fie mai mare decât valoarea minimă şi mai mică decât valoarea maximă). Modul de vizualizare al obiectului ''Slider'' este definit de metodele ''setShowTickMarks()'' şi ''setShowTickLabels()'' care primesc ca argumente parametri de tip ''boolean''. Metoda ''setBlockIncrement()'' precizează distanţa cu care se mişcă cursorul atunci când utilizatorul apasă pe pista asociată. În cazul în care se doreşte alinierea cursorului cu marcajele din cadrul pistei, se poate folosi metoda ''setSnapToTicks()''. Valoarea la care se găseşte cursorul poate fi obţinută prin metoda ''getValue()'' şi este de obicei de tip ''double''. === ProgressBar / ProgressIndicator === Prin clasele ''ProgressBar'' și ''ProgressIndicator'' se indică utilizatorului faptul că o sarcină este în curs de execuţie, precizându-se şi care este cantitatea din cadrul acesteia care a fost terminată deja. ''ProgressBar'' reprezintă o subclasă a ''ProgressIndicator''. Obiectele ''ProgressBar'' au forma unei bare. Obiectele ''ProgressIndicator'' sunt de forma unei diagrame circulare al cărui conţinut se umple pe măsură ce procentul este mai mare. Constructorul acestor clase primeşte ca parametru procentul în care sarcina a fost completată (alternativ se poate folosi metoda ''setProgress()''). Valoarea folosită ca parametru pentru constructorul claselor ''ProgressBar'' / ''ProgressIndicator'' sau pentru metoda ''setProgress()'' este subunitară. Există posibilitatea ca obiectele să fie create fără parametru, în cazul în care nu se poate evalua dimensiunea sarcinii în cauză. Într-o astfel de situaţie, metodele pot primi ca parametru şi o valoare negativă. Metoda ''isIndeterminate()'' se utilizează pentru a verifica dacă progresul este într-o stare nedeterminată sau nu. Clasele mai pun la dispoziţia programatorilor metoda ''progressProperty'' care întoarce valoarea progresului respectiv şi pe care este aplicat obiectul ascultător în cazul modificărilor acesteia. === Hyperlink === Clasa ''Hyperlink'', derivată din ''Labeled'' (din care moşteneşte metodele ''setText()'' şi ''setGraphic()'') este folosită spre a formata texte care au semnificaţia legăturilor Internet. Starea unei legături (vizitată / nevizitată) poate fi marcată prin metoda ''setVisited()''. Acţiunea asociată operaţiei de apăsare poate fi precizată prin metoda ''setOnAction()'' care primeşte un obiect de tip ''EventHandler'' (având definită metoda ''handle()'' corespunzătoare). La aceesarea unui astfel de obiect, locaţia poate fi vizualizată printr-un obiect ''WebEngine'' (obţinut ca ''new WebView().getEngine()'') pentru care se apelează metoda ''load()''. Hyperlink hyperlink = new Hyperlink(); String URL = "http://aipi2015.andreirosucojocaru.ro"; hyperlink.setText(URL); hiperlink.setOnAction((ActionEvent actionEvent) -> { new WebView().getEngine().load(URL); } ); === HTMLEditor === Controlul ''HTMLEditor'' este un editor de texte cu numeroase funcţionalităţi pentru redactarea documentelor HTML5: * formatare de text (bold, italic, underline, strike though); * configurări referitoare la paragraf (formatare, tip și dimensiune caractere); * culori de fundal; * indentare de text; * liste numerotate şi nenumerotate; * aliniere de text; * folosirea unei rigle orizontale; * facilităţi copy/paste pentru fragmente de text. În cadrul editorului este redat conţinutul unei pagini Internet asociat unui cod HTML. Constructorul unui obiect ''HTMLEditor'' nu primeşte parametri, dimensiunea sa putând fi specificată prin metodele ''setPrefWidth()'' / ''setPrefHeight()''. Conţinutul (o pagină Internet dată prin codul HTML) va fi specificat prin metoda ''setHtmlText()''. Acesta poate fi obţinut folosind metoda "pereche" ''getHtmlText()''. Vizualizarea conţinutului acestui editor într-un navigator poate fi realizată tot prin metoda ''load()'' a unui obiect ''WebEngine'', ca în cazul obiectul ''Hyperlink''. Conținutul corespunzător unui cod HTML afișat de un obiect ''HTMLEditor'' și de un navigator ''WebEngine'' pentru care se apelează metoda ''load()'' ar trebui să fie identic. Fiind derivat din clasa ''Node'', acestui tip de obiect i se pot aplica stiluri, efecte vizuale sau transformări. === Tooltip === Obiectele ''Tooltip'' sunt folosite pentru texte explicative ce pot fi asociate oricăror controale JavaFX din pachetul ''javafx.scene.control'' prin apelul metodei ''setTooltip()''. Starea sa poate fi activat şi vizibil (un obiect ''Tooltip'' care este vizibil este în același timp și activat), existând de regulă o întârziere între cele două momente (plasarea mouse-ului asupra obiectului şi afişarea propriu-zisă). Fiind derivată din ''Labeled'', i se poate asocia nu numai un text, ci şi o imagine. Legătura dintre un control de tip ''Node'' şi obiectul ''Tooltip'' asociat poate fi eliminată prin metoda ''uninstall()'' care primeşte ca argumente elementele care se doresc decuplate. === TitledPane / Accordion === Obiectele de tip ''TitledPane'' reprezintă panouri care au asociat şi un titlu, putând fi deschise şi închise, încapsulând orice obiect de tip ''Node'' (controale, grupuri de elemente din cadrul unui container). Obiectele ''TitledPane'' pot fi construite primind ca parametri textul / obiectul de tip ''Node'', acestea putând fi specificate ulterior prin metodele ''setText()'', respectiv ''setContent()''. Implicit, orice obiect TitledPane poate fi închis şi deschis (proprietate ce poate fi specificată prin metoda ''setCollapsible()''), acţiunea realizându-se în mod animat (metoda ''setAnimated()'' indică acest comportament). Se recomandă ca înălţimea (minimă / maximă sau preferată) a unui obiect de tipul ''TitledPane'' să nu fie specificată întrucât acest fapt poate conduce la evenimente nedorite în cazul în care acesta este expandat. Dimensiunea obiectului este ajustată astfel încât să poată fi vizualizat întreg conţinutul său. Gruparea obiectelor ''TitledPane'' se face prin clasa ''Accordion'', acestea putând fi astfel afişate împreună. Un obiect ''Accordion'' conţine astfel mai multe obiecte ''TitledPane'' cu proprietatea că unul singur dintre acestea poate fi vizualizat la un moment dat. Obiectul ''TitledPane'' vizualizat în mod curent este accesat prin metodele ''{set/get}ExpandedPane()''. Obiectul de tip ascultător este aplicat obiectului întors de metoda ''expandedPaneProperty()'' şi este de tipul ''ChangeListener''. Accordion accordion = new Accordion(); TitledPane titledPane1 = new TitledPane(), titledPane2 = new TitledPane(); // ... titledPane1.setContent(...); titledPane2.setContent(...); accordion.getPanes().add(titledPane1); accordion.getPanes().add(titledPane2); accordion.expandedPaneProperty().addListener( (ObservableValue observableValue, TitledPane oldValue, TitledPane newValue) -> { // ... } ); === ColorPicker === Controlul ''ColorPicker'' este un element de interfaţă cu utilizatorul care permite selecţia unei culori dintr-o anumită gamă dar şi specificarea acesteia prin parametrii HSB (Hue/Saturation/Brightness) sau RGB (Red/Green/Blue). Obiectul conţine: * o listă pentru selecţia culorii - un obiect de tip ''ComboBox'' având un indicator de culoare și o etichetă corespunzătoare; * o paletă de culori predefinite; în cazul în care au fost definite culori de către utilizator, acestea sunt incluse într-o secțiune distinctă; navigarea în cadrul paletei de culori poate fi realizată prin folosirea tastelor sus, jos, stânga, dreapta; * fereastra de dialog pentru specificarea parametrilor acesteia (aceasta este deschisă în mod automat atunci când este selectată o anumită valoare din paleta de culori sau atunci când se selectează opţiunea ''"Custom Color..."''); utilizatorii își pot defini o culoare: * prin mişcarea mouse-ului asupra suprafeţei de culoare şi asupra barei de culori, parametrii HSB / RGB fiind modificaţi în mod automat; * specificând parametrii HSB / RGB sau Web (#, urmat de 6 cifre hexa)); * transparenţa culorii poate fi specificată prin intermediul parametrul ''Opacity'', care poate avea valori cuprinse între 1 şi 100 Un element grafic de tip ''ColorPicker'' se poate crea fără parametri sau poate fi indicată o anumită culoare considerată curentă (aceasta poate fi gestionată şi prin intermediul metodelor ''{get/set}Value()''). Clasa ''Color'' pune la dispoziţie mai multe culori predefinite care au denumiri specifice sau, pentru crearea de culori predefinite pot fi folosite metodele statice ''rgb()'' / ''hsb()'' (care primesc cei 3 parametri sau opţional 4, dacă se doreşte şi precizarea transparenţei) respectiv metoda statică ''web()'' care primeşte un şir de caractere. Culorile definite de utilizator pot fi obţinute folosindu-se metoda ''getCustomColors()'' al cărui rezultat este un obiect de tipul ''ObservableList''. Culorile definite de utilizator nu pot fi reîncărcate la repornirea aplicaţiei decât în situaţia în care este implementat un mecanism de persistenţă. Implicit, aspectul unui astfel de obiect este cel de ''ColorPickerSkin'' (din pachetul ''javafx.scene.control.skin''). Alternativ, pot fi precizate stiluri asemănătoare obiectelor ''ArrowButton'' sau ''SplitButton'', folosind clasele CSS corespunzătoare, sau poate fi redefinită proprietatea ''-fx-skin'' a clasei CSS ''.color-picker''. colorPicker.getStyleClass().add("arrow-button"); colorPicker.getStyleClass().add("split-button"); Încărcarea unei foi de stil care să fie valabilă pentru întreaga scenă poate fi realizată prin metoda ''scene.getStyleSheets().add("sceneStyleSheet.css")''. === DatePicker === Obiectul ''DatePicker'' este un control care permite selecția unei date dintr-un anumit calendar. El constă dintr-un obiect de tip ''ComboBox'' (editabil) având un câmp dată calendaristică și o zonă de selecție a unei date dintr-un calendar. Constructorul unui obiect de tip ''DatePicker'' poate să nu primească nici un parametru, sau poate conține un parametru de tip ''LocalDate'' indicând data calendaristică selectată în mod implicit (alternativ, poate fi apelată metoda ''setValue()''). Specificarea datei curente se face printr-un apel al metodei ''LocalDate.now()''. O dată calendaristică este afișată în formatul definit de sistemul local și de sistemul de calendare ISO (de regulă ''mm/dd/yyyy''). Proprietatea ''converter'' și metoda ''setConverter()'' permit specificarea unui format alternativ pentru o dată calendaristică (un parametru ''null'' restaurează formatul implicit). private DatePicker datePicker = new DatePicker(LocalDate.now()); private final String dateFormat = "dd.mm.yyyy"; StringConverter converter = new StringConverter() { DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(dateFormat); @Override public String toString(LocalDate localDate) { if (localDate != null) { return dateTimeFormatter.format(localDate); return ""; } @Override public LocalDate fromString(String string) { if (string != null && !string.isEmpty()) return LocalDate.parse(string, dateTimeFormatter); return null; } }; datePicker.setConverter(converter); datePicker.setPromptText(dateFormat.toLowerCase()); Afișarea săptămânii în cadrul calendarului se face prin metoda ''setShowWeekNumbers()'' care primește un parametru de tip ''boolean''. Schimbarea funcționalității unui obiect de tip ''DatePicker'' se face prin implementarea mecanismului de tip fabrică de celule, care generează obiecte de tip ''DateCell'' având un comportament corespunzător cerințelor funcționale. datePicker.setDayCellFactory(new Callback() { @Override public DateCell call(final DatePicker ) { return new DateCell() { @Override public void updateItem(LocalDate item, boolean empty) { super.updateItem(item, empty); // ... } }; } }); Astfel, pot fi dezactivate anumite date calendaristice (printr-un apel al metodei ''setDisable()'') care nu pot fi selectate pentru a se menține consistența informațiilor introduse. Un obiect de tip ''DatePicker'' suportă și sisteme de calendare non-ISO, cum ar fi cronologiile japoneze, Hijrah, Minguo și tailandeze-budiste. Precizarea unui sistem de măsurare a timpului alternativ se face prin intermediul metodei ''setChronology()''. Operațiile cu obiecte de tip dată calendaristică sunt definite în pachetul ''java.time'', putând fi utilizate clasele care definesc obiecte corespunzătoare diferitelor sisteme de calendare definite de ISO (//eng.// International Organization for Standardization): * ''Clock'' - un ceas ce oferă acces la momentul curent, dată, timp folosind o anumită zonă de timp; * ''Duration'' - o cantitate de timp; * ''Instant'' - un punct specific din cadrul unei cronologii; * ''LocalDate'' - o dată calendaristică fără zona de timp în sistemul de calendare definit de ISO-8601; * ''LocalDateTime'' - o dată calendaristică și un moment de timp fără zona de timp în sistemul de calendare definit de ISO-8601; * ''LocalTime'' - un moment de timp fără zona de timp în sistemul de calendare definit de ISO-860; * ''MonthDay'' - o zi din cadrul unei luni în sistemul de calendare definit de ISO-8601; * ''OffsetDateTime'' - o dată calendaristică și un moment de timp cu diferența de timp față de timpul Greenwich/UTC în sistemul de calendare definit de ISO-8601; * ''OffsetTime'' - un moment de timp cu diferența de timp față de timpul Greenwich/UTC în sistemul de calendare definit de ISO-8601; * ''Period'' - o perioadă de timp bazată pe date calendaristice; * ''Year'' - un an în sistemul de calendare definit de ISO-8601; * ''YearMonth'' - un an-lună în sistemul de calendare definit de ISO-8601; * ''ZonedDateTime'' - o dată calendaristică și un moment de timp cu zona de timp în sistemul de calendare definit de ISO-8601; * ''ZoneId'' - identificatorul unei zone de timp; * ''ZoneOffset'' - diferența de timp față de timpul Greenwich/UTC. === PaginationControl === Clasa ''Pagination'' oferă posibilitatea de a naviga între mai multe pagini corespunzând unui conţinut împărţit în mai multe secţiuni, fapt ce este util mai ales pentru dispozitivele mobile unde suprafaţa de afişare este limitată, iar interfaţa grafică nu poate fi de cele mai multe ori încadrată în aceasta. Controlul este format din conţinutul propriu-zis al paginii (redat în funcţie de logica aplicaţiei) şi din zona de navigare. În zona de navigare se găsesc butoane pentru deplasarea înainte şi înapoi, butoane pentru fiecare pagină în parte (cel selectat fiind corespunzător paginii curente). Totodată, pagina curentă este specificată şi printr-o etichetă având forma "pagina curentă / număr total de pagini". Navigarea se face fie prin accesarea butoanelor înainte şi înapoi, fie prin accesarea butonului corespunzător paginii în cauză. Constructorul unui obiect de tip ''Pagination'' poate fi apelat şi fără parametri (caz in care numărul total de pagini are valoarea ''INDETERMINATE''), poate primi 1 parametru indicând numărul total de pagini sau 2 parametri, indicându-se şi numărul paginii selectate. În cazul specificării unei pagini curente, numerotarea începe întotdeauna de la 0. Alternativ, pot fi folosite metodele ''setPageCount()'', respectiv ''setCurrentPageIndex()''. Nu se poate adăuga conţinut asociat unei pagini decât prin intermediul unei "fabrici" de pagini, folosindu-se metoda ''setPageFactory()'' care va folosi o metodă de tip callback. Aceasta va fi apelată de fiecare dată când este selectată o pagină. În situația în care nu există indexul de pagină selectat, valoarea întoarsă trebuie să fie ''null''. int totalNumberOfPages = ...; int currentPageNumber = 0; Pagination pagination = new Pagination(totalNumberOfPage, currentPageNumber); Pagination.setPageFactory((Integer currentPage) -> { // ... } ); Metoda ''call()'' generează un obiect de tip ''Node'' care reprezintă conţinutul ce va fi ataşat paginii în cauză. Se recomandă ca în cadrul acestei metode să se verifice faptul că parametrul cu care se apelează nu depăşeşte numărul maxim de pagini. În condiţiile în care nu pot fi afişate în zona de navigare butoane corespunzând tuturor paginilor, se poate utiliza metoda ''setMaxPageIndicatorCount()'' care limitează dimensiunea numărului de legături vizibile către pagini. Dacă se doreşte ca în locul numărului paginii respective să se afişeze alt tip de indicator, se poate schimba tipul clasei CSS corespunzătoare. Spre exemplu, în locul numerelor se pot folosi puncte, situaţie în care clasa CSS aferentă este ''Pagination.STYLE_CLASS_BULLET''. Alte atribute care pot fi precizate pentru a modifica stilul sunt: * ''-fx-max-page-indicator-count'' – specifică numărul maxim de indicatori de pagină; * ''-fx-arrows-visible'' – indică vizibilitatea săgeţilor care fac legătura cu paginile anterioară şi următoare; în mod implicit, are valoarea ''true''; * ''-fx-tooltip-visible'' – precizează vizibilitatea informaţiei explicative asociate indicatorului de pagină; în mod implicit, are valoarea ''true''; * ''-fx-page-information-visible'' – setează vizibilitatea informaţiilor referitoare la pagină; în mod implicit, are valoarea ''true''; * ''-fx-page-information-alignment'' – reprezintă modul în care va fi aliniată secţiunea corespunzătoare informaţiilor cu privire la pagină. === FileChooser === Controlul ''FileChooser'' permite utilizatorilor să navigheze prin sistemul de fişiere. El poate fi folosit pentru deschiderea unuia sau mai multor fişiere, respectiv pentru salvarea pe disc a unor anumite conţinuturi. Ulterior creării unui astfel de obiect (şi asocierii unui titlu sugestiv), acesta poate fi afişat în cadrul scenei respective în momentul în care este necesar, potrivit interacţiunii cu utilizatorul. Look and feel-ul acestui tip de control este corespunzător sistemului de operare pe care este apelat, pentru acele sisteme de operare care îl au implementat. FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Open File"); // ... File file = fileChooser.showOpenDialog(stage); FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Open Files"); // ... List fileList = fileChooser.showOpenMultipleDialog(stage); Obiectul de tip ''FileChooser'' poate fi configurat şi în privinţa directorului pe care îl deschide, prin apelarea metodei ''setInitialDirectory()'' care primeşte ca parametru un obiect de tip ''File''. În cazul în care se doreşte să se lase utilizatorului posibilitatea de a preciza directorul implicit, se poate folosi un obiect ''DirectoryChooser'', iar rezultatul metodei ''showDialog()'' (de tip ''File'') să fie pasat metodei ''setInitialDirectory()'' asociat obiectului de tip ''FileChooser''. Alternativ, se poate folosi directorul specific utilizatorului autentificat în cadrul sistemului de operare, obținut prin metoda: ''System.getProperty("user.home");''. Metoda ''showOpenDialog()'' întoarce o valoare care specifică fişierul care a fost selectat de către utilizator sau ''null'' în cazul în care nu s-a realizat nici o selecţie. Această metodă este folosită în situaţia în care se doreşte să se poată selecta un singur fişier. Metoda ''showOpenMultipleDialog()'' este utilizată în condițiile în care se dorește selectarea mai multor fișiere concomitent; aceasta va întoarce o listă a fişierelor care au fost selectate de utilizator sau ''null'' dacă nu s-a realizat nici o selecţie. Metodele ''showOpenDialog()'' și ''showOpenMultipleDialog()'' sunt blocante şi nu întorc nici un rezultat până când utilizatorul nu furnizează un răspuns. Vizualizarea conţinutului unui fişier (folosind aplicaţia asociată în mod implicit) se face prin clasa ''java.awt.Desktop'' care pune la dispoziţie metoda ''open()''. Înainte de a folosi funcţionalităţile puse la dispoziţie de această clasă trebuie să se verifice dacă platforma pe care se rulează le suportă. În acest sens, se poate folosi metoda statică ''isDesktopSupported()''. Alte metode implementate de clasa ''Desktop'' sunt ''browse()'' (pentru a deschide navigatorul implicit folosind o adresă dată ca parametru), ''edit()'' (pentru editarea conţinutului unui fişier), ''mail()'' (pentru transmiterea unui mesaj electronic către o adresă specificată în câmpul ''mailto'') şi ''print()'' (care tipăreşte la imprimanta implicită un fişier). Unui astfel de obiect i se pot aplica filtre cu privire la tipul fişierelor care vor fi afişate. Aceasta se face adăugând obiecte de tip ''ExtensionFilter'' extensiilor deja existente la obiectul de tip ''FileChooser'' (lista acestora putând fi obţinută prin metoda ''getExtensionFilters()''). Constructorul clasei ''ExtensionFilter'' primeşte ca parametri un şir de caractere sugestiv pentru tipul de fişiere respectiv şi o listă de extensii în formatul ''*.ext'' (de exemplu ''*.*'', ''*.docx'', ''*.pdf'', ''*.jpg'', ''*.png''). FileChooser fileChooser = new FileChooser(); fileChooser.setTile("File Chooser"); fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); FileChooser.ExtensionFilter allFiles = new FileChooser.ExtensionFilter("All Files", "*.*"); FileChooser.ExtensionFilter documents = new FileChooser.ExtensionFilter("Documents", "*.docx", "*.pdf"); FileChooser.ExtensionFilter images = new FileChooser.ExtensionFilter("Images", "*.jpg", "*.png"); fileChooser.getExtensionFilters().addAll(allFiles, documents, images); Dacă se doreşte salvarea unui conţinut sub forma unui fişier pe disc, obiectul de tip ''FileChooser'' trebuie apelat cu metoda ''showSaveDialog()''. Acesta întoarce tot un obiect de tip ''File'' care poate fi folosit la scrierea unui conţinut. === Controale Definite de Utilizator === În situaţia în care un control predefinit nu corespunde necesităţilor, se pot crea controale definite de utilizator ce extind clasa ''javafx.scene.control.Control''. De asemenea, comportamentul implicit al unor controale predefinite poate fi suprascris (chiar la creearea acestora) redefinind metodele al căror mod de operare nu corespunde cerinţelor. Pentru obiectele ''TableView'', ''ListView'', ''TreeView'', ''ComboBox'' se pot defini "fabrici" de celule pentru generarea de conținut având o anumită funcționalitate. În acest sens, se folosește metoda ''setCellFactory()'' ce primește ca parametru un obiect de tip ''Callback'' pentru care se suprascrie metoda ''call()'', întorcând controalele grafice dorite. === Compatibilitatea JavaFX cu Dispozitive Mobile === Pentru dispozitivele mobile, JavaFX implementează operații specifice: * apăsare (doar pentru ecran tactil, o singură apăsare la un moment dat); * gesturi (atât pentru ecran tactil cât și pentru trackpad, doar glisare). De asemenea, poate fi utilizată o tastatură virtuală de fiecare dată când obiectul curent conține un câmp pentru introducerea de text. Formatul acesteia poate fi particularizat prin intermediul proprietății ''vkType'' care poate lua valorile ''numeric'', ''url'', ''email'' sau ''text'' (implicit). Unele elemente grafice au un comportament modificat pentru dispozitivele mobile: * ''ScrollBar'', ''ScrollPane'' - elementele derulante nu sunt afișate dacă utilizatorul nu interacționează cu controlul respectiv; schimbarea porțiunii vizibile se face ca urmare a gesturilor de apăsare iar elementele derulante au doar rolul de a indica poziția curentă; * ''TextField'', ''PasswordField'', ''TextArea'', ''DatePicker'' - este vizualizat cursorul; * ''ContextMenu'' - meniul contextual este dispus orizontal. ^ **Control** ^ **Acțiunea Utilizatorului** ^ **Comportament** ^ | ''Button'',\\ ''Hyperlink'',\\ ''MenuButton'',\\ ''ToggleButton'' | atingere | activează butonul | | ''CheckBox'' | atingere (//eng.// tap) | comută între stările selectat / deselectat | | ''ComboBox'' | atingere pictogramă | afișează sau ascunde lista activă; elementul afișat în câmpul text este selectat când lista activă este vizibilă | | ::: | atingere câmp text | ♦ controale non-editabile: afișează lista activă | | ::: | ::: | ♦ controale editabile: plasează cursorul în câmpul text | | ::: | atingere element din cadrul listei | închide lista activă și salvează valoarea sa | | ::: | atingere element din afara listei | închide lista | | ::: | tragere (//eng.// drag) | pornește defilarea conținutului | | ::: | plasare (//eng.// drop) | oprește defilarea conținutului | | ::: | glisare (//eng.// swipe) | defilare rapidă a conținutului | | ''ListView'' | tragere | pornește defilarea conținutului | | ::: | plasare | oprește defilarea conținutului | | ::: | glisare | defilare rapidă a conținutului | | ::: | atingere | selectează un element și activează acțiunea asociată acestuia (oprește defilarea conținutului, dacă este cazul) | | ::: | dublă atingere | dacă este activat, intră în modul de editare | | ''TextField'',\\ ''PasswordField'',\\ ''TextArea'' | atingere | poziționează cursorul | | ::: | dublă atingere | selectează conținutul | | ::: | atingere și așteptare | deschidere meniul contextual | | ::: | glisare | defilarea rapidă a conținutului | | ::: | tragere | pornește defilarea conținutului | | ::: | plasare | oprește defilarea conținutului; dacă se depășesc limitele controlului, conținutul va fi deselectat iar cursorul plasat în cadrul elementului grafic | | ''RadioButton'' | atingere | dacă controlul este selectat, nu se realizează nici o acțiune\\ dacă controlul nu este selectat, îl selectează, deselectând celelalte elemente din cadrul grupului | | ''ScrollBar'',\\ ''ScrollView'' | tragere | pornește defilarea conținutului | | ::: | plasare | oprește defilarea conținutului | | ::: | glisare | defilarea rapidă a conținutului | | ::: | atingere | selectează un element și activează acțiunea asociată acestuia (oprește defilarea conținutului, dacă este cazul) | | ''TableView'',\\ ''TreeView'' | atingere | ''TableView'' - selectează celula sau invocă acțiunea asociată acesteia (dacă este cazul);\\ ''TreeView'' - expandează sau pliază nodul | | ::: | dublă atingere | intră în modul de editare dacă această operație este suportată de celula respectivă | | ::: | tragere | pornește defilarea conținutului | | ::: | plasare | oprește defilarea conținutului | | ::: | glisare | defilarea rapidă a conținutului | | ''ColorPicker'' | atingere scurtă în câmpul de culori sau a culorilor definite de utilizator în paleta de culori | actualizează culoarea în câmpul de culori; închide paleta de culori; aplică culoarea | | ::: | atingere în afara paletei de culori | închide paleta de culori | | ::: | atingere în orice punct în câmpul de culori sau în fereastra pentru definirea de culori | actualizează valorile în panourile RGB / HSB / Web; actualizează noua culoare în zona de previzualizare a culori | | ::: | atingerea în orice punct din panoul pentru specificarea culorii în fereastra pentru definirea de culori | actualizează valorile în panourile RGB / HSB / Web; actualizează noua culoare în fereastra pentru definirea de culori | | ::: | atingerea în orice punct din cadrul pistei de derulare\\ tragerea cursorului de derulare spre stânga sau dreapta | închide fereastra de dialog; închide paleta de culori; actualizează culoarea în câmpul de culori; aplică culoarea | | ''Pagination'' | atingerea unui buton corespunzător paginii | deschide pagina selectată | | ::: | atingerea butonului următor\\ glisare la stânga | deschide pagina următoare | | ::: | atingerea butonului anterior\\ glisare la dreapta | deschide pagina anterioară | | ''DatePicker'' | atingerea câmpului corespunzător datei calendaristice | afișează cursorul în cadul câmpului; afișează tastatura virtuală | | ::: | atingerea pictogramei corespunzătoare calendarului | afișează / ascunde calendarul | | ::: | atingerea săgeților stânga / dreapta din cadrul calendarului | afișează luna / anul anterior / următor | | ::: | atingerea oricărei date calendaristice din cadrul calendarului | actualizează câmpul cu data selectată și închide calendarul | | ::: | atingerea oricărui punct în afara calendarului | închide calendarul fără a actualiza câmpul | Pe dispozitivele mobile nu este posibilă: * redimensionarea / rearanjarea coloanelor și nici ordonarea datelor dintr-un tabel; * selecția multiplă în liste sau arbori; * suport pentru copierea textului în câmpuri pentru introducerea parolelor. Totodată, unele funcționalități ale clasei ''Stage'' (specificarea unui titlu, redimensionarea, posibilitatea de particularizare), navigatorul pentru pagini Internet și redarea de conținut multimedia folosind JavaFX nu sunt implementate pentru dispozitivele mobile. ==== Gestiunea Meniurilor ==== Un meniu este o listă de elemente acționabile care sunt făcute vizibile la solicitarea utilizatorului. La un moment dat, se poate selecta un singur element din cadrul unui meniu, ulterior acestei acțiuni meniul devenind din nou invizibil. Utilizând meniuri, se optimizează spațiu în interfața cu utilizatorul întrucât acestea conțin funcționalități care nu sunt afișate tot timpul. În JavaFX pot fi definite mai multe tipuri de meniuri: * ''MenuBar'' - meniul principal al unei aplicații * ''MenuItem'' - pentru asocierea unei acțiuni * ''Menu'' - pentru implementarea unui submeniu * ''CheckMenuItem'' - pentru elemente a căror stare are o valoare binară * ''RadioMenuItem'' - pentru selecţii mutual exclusive * ''CustomMenuItem'' - pentru tipuri de meniu definite de utilizator * ''SeparatorMenuItem'' - pentru separarea intrărilor din cadrul unui meniu * ''ContextMenu'' - pentru meniuri asociate unor controale grafice De regulă, se construieşte un obiect ''MenuBar'', se definesc categoriile acestuia care sunt populate cu elemente de tip ''Menu'', fiecare dintre acestea conținând intrări de tip ''MenuItem'' (sau subtipurile acestuia) corespunzând unei opţiuni acţionabile. Frecvent, obiectul de tip ''MenuBar'' ocupă partea de sus a scenei, fiind redimensionat la spaţiul disponibil. Constructorul clasei ''MenuBar'' nu primește nici un parametru. Asocierea categoriilor din cadrul acestui tip de meniu se face prin metodele ''add()'' / ''addAll()'' apelabile pe lista observabilă de meniuri, întoarsă de metoda ''getMenus()''. MenuBar containerMenu = new MenuBar(); Menu fileMenu = new Menu("File"); Menu operationsMenu = new Menu("Operations"); Menu helpMenu = new Menu("Help"); containerMenu.getMenus().addAll(fileMenu, operationsMenu, helpMenu); Un obiect de tip ''MenuItem'' poate avea asociat un text și o pictogramă, specificate în momentul în care acesta este construit. Pentru acest tip de obiect se va preciza o acțiune, prin intermediul metodei ''setOnAction()'' ce primește ca parametru un obiect de tip ''EventHandler'' pentru care se implementează metoda ''handle()''. Apartenența sa la un obiect de tip ''Menu'' se face prin metodele ''add()'' / ''addAll()'' apelabile pe lista observabilă de intrări ale acestuia, întoarsă de metoda ''getItems()''. Pentru un obiect ''MenuItem'', se poate asocia o combinaţie de taste având acelaşi efect ca şi accesarea propriu-zisă a acestuia (folosind mouse-ul), apelând metoda ''setAccelerator()'' care primește un parametru obţinut din metoda statică ''keyCombination()'' a clasei omonime (combinaţia are forma "Ctrl+...", "Alt+...", "Shift+..."). O intrare de tipul ''MenuItem'' a unui meniu poate fi dezactivată printr-un apel al metodei ''setDisable()''. O intrare dezactivată a unui meniu este continuare vizibilă, fără ca utilizatorul să aibă posibilitatea de a o selecta. Menu operationsMenu = new Menu("Operations"); MenuItem databaseManagementMenu = new MenuItem("DataBase Management"); databaseManagementMenu.setAccelerator(KeyCombination.keyCombination("Ctrl+D")); operationsMenu.getItems().addAll(databaseManagementMenu); Obiectele de tip ''MenuItem'' nu extind clasa ''Node'', astfel încât nu pot fi incluse direct în scena aplicației și nici nu sunt vizibile până când nu sunt asociate unui obiect de tip ''MenuBar''. Obiectele ''CheckMenuItem'' pot avea starea selectat / deselectat, valoare accesibilă din proprietatea ''selectedProperty()'' (pentru care se și specifică metoda de tip ascultător ce monitorizează schimbările de la acest obiect) și prin intermediul metodelor ''{get|set}Selected()''. Dacă este selectată, intrarea dintr-un astfel de meniu este însoțită de simbolul ✔, dacă este deselectată, se afișează doar textul / imaginea asociate. Obiectele ''RadioMenuItem'' sunt de obicei asociate unui obiect ''ToggleGroup'' (prin metoda ''setToggleGroup()'') care implică selecţia lor mutual exclusivă. Elementul selectat în mod curent poate fi gestionat prin metodele ''{get|set}SelectedToggle()''. Tratarea evenimentelor ce vizează modificarea selecției la nivelul grupului de obiecte ''RadioMenuItem'' se face prin asocierea unei metode ascultător pentru proprietatea ''selectedToggleProperty()''. Dacă spaţiul de afișare nu permite utilizarea unor meniuri de tip ''MenuBar'' / ''MenuItem'' se pot folosi meniuri contextuale (''ContextMenu''), atașabile oricărui tip de control JavaFX. Acestea devin vizibile atunci când este apelată metoda ''show()'' asociată de regulă la accesarea unui control (printr-un click de mouse) căruia i-a fost asociat meniul. Afişarea meniului contextual se face la coordonatele la care a fost înregistrat click-ul pe mouse (acestea fiind obținute prin apelul metodelor ''getScreenX()'' / ''getScreenY()'' pe evenimentul generat). Obiectele ''CustomMenuItem'' sunt folosite atunci când intrările meniului utilizează alte tipuri de controale, derivate din clasa ''Node''. === Exemplu === În cadrul unui fișier FXML se definesc un meniu principal (obiect ''MenuBar'' denumit ''containerMenu'') și o categorie / submeniu (obiect de tip ''Menu'' denumit ''databaseManagementMenu''). Pe baza denumirii tabelelor dintr-o bază de date se construiesc intrări în cadrul meniului (obiecte de tip ''MenuItem''), ale căror evenimente vor fi tratate în cadrul aceleiași clase, asociate ulterior categoriei / submeniului. @FXML private MenuBar containerMenu; @FXML private Menu databaseManagementMenu; // ... @FXML private void initialize() { try { DataBaseWrapper databaseWrapper = DataBaseWrapperImplementation.getInstance(); ArrayList tableNames = databaseWrapper.getTableNames(); for (String tableName: tableNames) { MenuItem menuItem = new MenuItem(Utilities.tableNameToMenuEntry(tableName)); menuItem.addEventHandler(EventType.ROOT, (EventHandler)this); databaseManagementMenu.getItems().add(menuItem); } } catch (SQLException sqlException) { System.out.println("An exception has occured: "+sqlException.getMessage()); if (Constants.DEBUG) sqlException.printStackTrace(); } // ... } {{ :laboratoare:laborator09:06menus.png?nolink }} ==== Diagrame ==== În JavaFX pot fi utilizate şi grafice, definite de pachetul ''javafx.scene.chart'', fiind suportate următoarele tipuri: * ''PieChart'' - diagramă în care datele sunt reprezentate sub forma unui cerc împărțit în secțiuni triunghiulare (arce de cerc) denumite felii; * ''LineChart'' - diagramă definită pe baza a două axe de coordonate în care datele sunt prezentate sub forma unei serii de puncte conectate prin linii drepte; * ''AreaChart'' - diagramă în care datele sunt reprezentate sub forma unor suprafețe între o serie de puncte conectate cu axele de coordonate prin linii drepte; * ''BubbleChart'' - diagramă definită pe baza a două axe de coordonate în care datele sunt prezentate sub formă de baloane; * ''ScatterChart'' - diagramă în care datele sunt prezentate sub forma unei mulțimi de puncte; * ''BarChart'' (cu varianta ''StackedBarChart'') - diagramă definită pe baza a două axe de coordonate în care datele sunt prezentate sub forma unor dreptunghiuri. În momentul în care se defineşte un model pentru un anumit tip de diagramă, trebuie să se distingă între acele tipuri de tabele care folosesc axele de coordonate şi cele care nu le utilizează. Proprietăţile comune tuturor tipurilor de grafice sunt titlul (''setTitle()''), poziţia sa (sus, jos, stânga, dreapta), vizibilitatea etichetelor (''setLabelsVisible()''), vizibilitatea (''setLegendVisible()'') şi poziţia (''setLegendSide()'' / ''setLabelLineLength()'') legendei. === Tipuri de Diagrame ce nu folosesc sistemul de coordonate === Graficele care nu folosesc sistemul de coordonate (pie), derivate din clasa ''PieChart'', folosesc ''PieChart.Data'' pentru a indica valorile fiecărei secţiuni. În acest tip de grafic, datele sunt afişate în ordinea acelor de ceasornic (modul de afişare putând fi schimbat prin metoda ''setClockwise()'') şi au un anumit unghi de pornire (ce poate fi modificat prin metoda ''setStartAngle()''). Constructorul clasei ''PieChart'' primește ca parametru o listă observabilă de obiecte de tip ''PieChart.Data'', definite sub forma unor asocieri între o etichetă (de tip șir de caractere) - denumirea categoriei respective și o valoare numerică. === Tipuri de Diagrame ce folosesc sistemul de coordonate === Graficele ce utilizează sistemul de coordonate (de tipul line, area, bubble, scatter, bar) sunt derivate din clasa ''XYChart'', modelul de date fiind specificat de clasa ''XYChart.Data''. Valorile aferente axelor de coordonate sunt reprezentate de proprietăţile ''xValue'', respectiv ''yValue''. De asemenea, pentru fiecare element al graficului se pot specifica valori suplimentare (proprietatea ''extraValue''). Acestea sunt specificate ca parametri în constructorul obiectului de tip ''XYChart.Data''. Constructorul unei diagrame ce folosește sistemul de coordonate primește ca parametrii cele două axe (obiecte de tipul ''Axis'': ''NumberAxis'' sau ''CategoryAxis''). Pentru aceste tipuri de grafice pot fi definite mai multe serii de date, folosind clasa ''XYChart.Series''. Fiecare serie de date poate avea un nume (indicată prin metoda ''setName()''), iar valorile sunt adăugate prin metoda ''add()'' apelabilă pe rezultatul metodei ''XYChart.Series.getData()'', acestea având tipul ''XYChart.Data''. Pentru fiecare axă poate fi specificată eticheta (prin metoda ''setLabel()''), poziţia relativă faţă de grafic (apelând metoda ''setSide()'' pentru obiectul de tip ''NumberAxis'' / ''CategoryAxis''), limitele inferioară şi superioară (''setLowerBound()'' / ''setUpperBound()''), distanţa dintre marcaje (''setTickUnit()''), valorile între care vor fi poziţionate (''setTickLabelGap()''), culoarea etichetelor (''setTickLabelFill()''), distanța între categorii (''setCategoryGap()''). Este posibilă și rotirea axelor de coordonate, astfel încât interpretarea valorilor reprezentate în cadrul diagramelor să se realizeze mai ușor: metoda ''setTickLabelRotation()'' primește ca parametru valoarea unghiului (exprimată în grade) cu care să se facă repoziționarea. Totodată, axele îşi pot determina unele proprietăţi din datele care le sunt asociate. În momentul în care sunt modificate proprietăţile axelor de coordonate sau din cadrul graficului se poate specifica faptul că operaţia să se realizeze în mod animat, printr-un apel al metodei ''setAnimated()''. Dacă datele conţinute în tabel sunt de tip numeric, axele cu care se crează acesta au tipul ''NumberAxis'', altfel se va folosi ''CategoryAxis''. CategoryAxis xAxis = new CategoryAxis(); NumberAxis yAxis = new NumberAxis(); yAxis.setTickLabelFormatter(new NumberAxis.DefaultFormatter(yAxis, "RON ", null)); LineChart lineChart = new LineChar(xAxis, yAxis); lineChart.setTitle("Profit Statistics"); XYChart.Series series2015 = new XYChart.Series(); series2015.setName("Year 2015"); series2015.getData().add(new XYChart.Data("January", ...)); // ... lineChart.getData.add(series2015); ==== ==== Un element al unui grafic poate avea asociată o metodă de tratare în cazul producerii unor evenimente, aceasta realizându-se similar ca pentru orice obiect din clasa ''Node''. Metodele de tratare trebuie adăugate pentru fiecare nod în parte, obţinut prin iterare pe rezultatul metodei ''chart.getData()''. Şi pentru o diagramă poate fi schimbat modul de vizualizare prin asocierea unei foi de stil sau a unei clase CSS. Proprietăţile CSS corespunzătoare sunt prefixate de regula de ''.default-colorx'' (cu x valoare numerică) şi pot avea valorile: * ''chart-area-symbol'' - parametrii simbolurilor din legenda diagramei pentru un set de date; * ''chart-series-area-line'' - parametrii liniilor drepte; * ''chart-series-area-fill'' - parametrii suprafețelor; * ''chart-symbol'' - simbolul folosit pentru reprezentarea unei valori. Utilizatorul îşi poate defini propriul tip de tabel (care extinde clasa ''Chart'') şi suprascriind metodele definite în această clasă. ==== Elemente de tip Text ==== În aplicaţiile JavaFX componentele text pot fi create ca instanţe ale clasei ''javafx.scene.text.Text'' (derivată din clasa ''Node''). Astfel, se pot aplica efecte sau transformări şi se pot crea animaţii folosind obiectul text respectiv. De asemenea, având în vedere că ''Node'' extinde ''Shape'', se poate specifica şi o culoare pentru fundalul textului, respectiv se poate aplica un efect de îngroşare pentru forma asociată lui. Constructorul unui obiect de tip ''Text'' poate primi (sau nu) textul respectiv, precum şi poziţia unde se doreşte a fi afişat. Pentru aceste obiecte se pot utiliza şi metodele ''setText()'', ''setFont()'' şi ''setColor()''. === === Parametrul metodei ''setFont()'' se obţine prin metoda statică ''font()'' a clasei cu acelaşi nume, specificându-se numele tipului de caractere (ca ''String'') şi dimensiunea acestuia. Se poate specifica utilizarea setului de caractere implicit, diferit de la o platformă la alta, acesta putând fi obținut print-un apel al metodei ''Font.getDefault()''. Se pot utiliza şi tipuri de caractere definite de utilizator (conţinute în fişiere de tip ''.ttf'' sau ''.otf'' aflate în directorul proiectului), acestea putând fi încărcate prin metoda ''loadFont()''. De asemenea, proprietăţile de tip bold / italic pot fi specificate ca parametri ai aceleiaşi metode, utilizându-se constantele ''FontWeight.BOLD'' respectiv ''FontPosture.ITALIC''. ==== ==== Text text = new Text(); text.setText("Sample Text"); text.setFont(Font.getDefault(),FontWeight.BOLD|FontPosture.ITALIC,24); text.setFontSmoothingType(FontSmoothingType.LCD); text.setFill(Color.BEIGE); Gruparea mai multor obiecte de tip ''Text'' se poate face prin intermediul clasei ''TextFlow'' care ignoră coordonatele elementelor pe care le conține, poziționându-le în funcție de proprietățile sale (lățime, înălțime și aliniere). Asocierea obiectelor de tip ''Text'' în cadrul unui obiect ''TextFlow'' se face prin apelul metodelor ''add()'' / ''addAll()'' pentru lista observabilă a componentelor sale. Se recomandă totuşi ca proprietățile unui text să fie precizate în foi de stil care apoi să fie încărcate prin intermediul metodei ''setId()'' (care primeşte ca parametru numele clasei CSS corespunzătoare). Efectele care se pot crea asupra textelor sunt: * ''PerspectiveTransform'', indicându-se cele 4 puncte ale formei ce defineşte transformarea de perspectivă ca perechi (x,y); * ''GaussianBlur'' pentru estomparea textului; * ''DropShadow'' pentru umbre exterioare - indicându-se un offset (''setOffsetX()'' / ''setOffsetY()''), o culoare (''setColor()''), raza umbrei (''setRadius()''), împrăştierea (''setSpread()''); * ''InnerShadow'' pentru umbre interioare; * ''Reflection'' pentru reflectarea conţinutului respectiv - indicându-se opacitatea de sus şi de jos (''setTopOpacity()'' / ''setBottomOpacity()''), fracţia din original care va fi vizibilă în reflecţie (''setFraction()''), un offset între original şi reflecţie (''setTopOffset()''). Aplicarea unui efect se face prin metoda ''setEffect()'', care primeşte ca parametru obiect de tipul transformării respective. Combinarea de efecte se face folosind clasa ''Blend'' (modul fiind în acest caz ''BlendMode.MULTIPLY''), efectele fiind specificate ca parametri ai metodelor ''setBottomInput()'', respectiv ''setTopInput()''. Atunci când se doreşte combinare a mai mult de 2 efecte, combinarea se va face între un efect propriu-zis şi alt obiect de tip Blend care conţine la rândul său o altă combinaţie. Text text = new Text(); Blend blend1 = new Blend(), blend2 = new Blend(); blend1.setMode(BlendMode.MULTIPLY); blend2.setMode(BlendMode.MULTIPLY); DropShadow dropShadow = new DropShadow(); //... blend1.setBottomInput(dropShadow); InnerShadow innerShadow = new InnerShadow(); // ... Reflection reflection = new Reflection (); // ... blend2.setBottomInput(innerShadow); blend2.setTopInput(reflection); blend1.setTopInput(blend2); text.setEffect(blend1); ==== Conținut pentru Pagini Internet ==== JavaFX dispune şi de un navigator încorporat, bazat pe WebKit, un motor open-source care implementează foi se stil CSS (//eng.// Cascading Stype Sheets), JavaScript, interfața de programare Document Object Model (DOM) și HTML5. === === Caracteristicile HTML5 suportate sunt: * etichetele Canvas și SVG pentru: * redarea de conținut grafic; * construirea de forme folosind sintaxa Scalable Vector Graphics, aplicarea de configurări referitoare la culoare, efecte vizuale, animații; * redarea de conținut media, codec-urile implementate fiind: * audio: AIFF, WAV(PCM), MP3, AAC; * video: VP6, H264; * containere media: FLV, FXM, MP4, MpegTS (HLS); * controale pentru redarea de formulare și procesarea datelor; * gesiunea istoricului de navigare din cadrul unei sesiuni, prin intermediul obiectului ''WebEngine''; * etichete interactive: ''details'', ''summary'', ''command'', ''menu''; * acces la modelul obiectual al unei pagini Internet (DOM), obținut prin intermediul obiectului ''WebEngine'' (metoda ''getDocument()''; * rularea de scripturi Web (//eng.// Web workers) concomitent cu realizarea altor activități din cadrul paginii Internet, astfel încât interacțiunea cu utilizatorul să nu fie afectată; * sockeți Web (clasa ''WebSocket'') pentru realizarea de comunicații bidirenționale cu diverse servere; * seturi de caractere declarate cu regula ''@font-face'' (//eng.// Web fonts) descărcate de pe alte servere, la nevoie (alegerea unui set de caractere care este adecvat scopurilor de proiectare pentru o pagină dată). ==== ==== Funcționalitățile implementate de navigatorul încorporat de JavaFX sunt: * redarea de conţinut HTML5 stocat local sau de la o adresă la distanţă; * obţinerea istoricului de vizitare; * executarea de comenzi JavaScript; * acces la metodele JavaFX din acest JavaScript; * gestiunea ferestrelor proeminente (//eng.// pop-up window); * aplicarea de efecte asupra sa. Acesta moşteneşte toate atributele şi metodele de la clasa ''Node'', având toate funcţionalităţile acesteia. Clasele pe care le foloseşte navigatorul se găsesc în pachetul ''javafx.scene.web''. **Clasa ''WebEngine''** oferă funcţionalitate de navigare de bază implementând interacţiunea cu utilizatorul cum ar fi accesarea unor legături Internet şi transmiterea de formulare HTML. Ea gestionează o singură pagină HTML la un moment dat, suportând operații precum încărcarea de conţinut Internet şi accesarea proprietăţilor DOM corespunzătoare precum şi execuţia unor comenzi JavaScript (această funcţionalitate poate fi dezactivată pentru anumite motoare web). O instanţă a acestei clase poate fi creată folosind un constructor fără parametri (conţinutul Internet aferent putând fi indicat prin metoda ''load()'') sau un constructor căruia i se transmite o locaţie. Utilizatorii pot indica foi de stil care le înlocuiesc pe cele implicite, folosite în cadrul paginilor Internet care sunt redate de navigator. **Clasa ''WebView''** reprezintă o extensie a clasei ''Node'', încapsulând obiectul ''WebEngine'', încorporând conţinutul HTML în scena unei aplicaţii şi oferind atributele şi metodele pentru a aplica efecte şi transformări. Legătura dintre acest obiect şi motorul asociat se face prin metodele ''{get|set}Engine()''. String URL = "http://aipi2015.andreirosucojocaru.ro"; WebView webView = new WebView(); WebEngine webEngine = webView.getEngine(); webEngine.load(URL); Modul în care sunt gestionate ferestrele proeminente poate fi specificat prin intermediul unei metode de tratare care poate întoarce acelaşi motor web ca cel curent (caz în care pagina respectivă este deschisă în acelaşi navigator) sau alt motor web. Clasa ''PopupFeatures'' descrie proprietățile unei ferestre proeminente (așa cum sunt descrise de specificația JavaScript), instanța sa fiind transmisă metodei de tratare a evenimentului de încărcare a unui conținut de acest tip. webEngine.setCreatePopupHandler(new Callbak() { @Override public WebEngine call(PopupFeatures configuration){ // ... } }); Componenta ''WebView'' reține informațiile cu privire la navigare în memorie, astfel încât acestea sunt pierdute atunci când aplicația este terminată. Persistența poate fi însă implementată în mod explicit prin clasa ''java.net.ResponseCache''. Clasa ''WebEngine'' pune la dispoziţie metoda ''executeScript()'' pentru a rula o comandă JavaScript declarată în cadrul documentului încărcat în navigator la momentul de timp respectiv. Rezultatul execuției metodei este convertit la un obiect ''java.lang.Object'', pe baza următoarelor reguli: * tipul JavaScript ''Int32'' este convertit la ''java.lang.Integer''; * numerele JavaScript sunt covertite la ''java.lang.Double''; * valorile de tip șir de caractere din JavaScript sunt convertite la ''java.lang.String''; * valorile de tip adevărat / fals din JavaScript sunt converite la ''java.lang.Boolean''. De asemenea, există posibilitatea de a invoca cod JavaFX din cadrul JavaScript prin crearea unui obiect de interfaţă în aplicaţie care este transmis prin metoda ''JSObject.setMember()''. Ea trebuie apelată într-o metodă de tratare asociată evenimentului de încărcare al paginii, care se realizează pe un fir de execuţie separat. Pentru acest obiect, atributele şi metodele publice definite în JavaFX vor fi accesibile din JavaScript. webEngine.getLoadWorker().stateProperty().addListener( (ObservableValue observableValue, State oldState, State newState) -> { if (newState == State.SUCCEEDED) { JSObject jsObject = (JSObject)webEngine.executeScript("window"); jsObject.setMember("myObject", new MyClass()); } // ... } ); // JavaScript interface object public class MyClass { public void myMethod() { // ... } } Un astfel de obiect poate fi folosit într-un document HTML, astfel: ... Pentru obiectele ''WebView'', meniurile contextuale sunt activate implicit, dezactivarea lor realizându-se prin metoda ''setContextMenuEnabled()''. Istoricul paginilor vizitate poate fi obţinut prin metoda ''getHistory()'' aplicată obiectului de tip ''WebEngine'', întorcând un obiect de tip ''WebHistory''. Acesta este o listă de intrări ce poate fi accesată prin metoda ''getEntries()'', conţinând informaţii despre pagină (URL şi titlu) sau despre data la care aceasta a fost vizitată cel mai recent. Metoda ''go()'' a acestei clase forțează navigatorul să încarce pagina reţinută la indexul respectiv. Conținutul unei pagini Internet poate fi tipărit folosint imprimanta instalată în cadrul mașinii, folosind metoda ''print()'' din clasa ''WebEngine''. Aceasta primește un obiect de tip ''PrinterJob'', obținut prin apelul metodei statice ''PrinterJob.createPrinterJob()''. În situația în care nu există nici o imprimantă instalată, rezultatul întors va fi ''null''. De asemenea, trebuie apelată metoda ''endJob()'' a obiectului ''PrinterJob'' pentru a se asigura faptul că procesul de tipărire a fost terminat. Fiecare sarcină de tipărire poate fi configurată prin specificarea parametrilor: ''collation'', ''copies'', ''pageLayout'', ''pageRanges'', ''paperSource'', ''printColor'', ''printResolution'', ''printQuality'', ''printSides''. În cazul în care se doreşte folosirea mai multor navigatoare, acestea pot fi create în cadrul unor obiecte de tip ''TabPane''. ==== Mecanisme de Dispunere a Conținutului ==== În JavaFX sunt disponibile mai multe mecanisme de dispunere a conținutului, denumite containere, care pot fi utilizate pentru gestiunea interfeței cu utilizatorul. În situația în care o fereastră este redimensionată, elementele componente sunt își modifică în mod automat coordonatele și mărimea, în funcție de proprietățile pe care le au. Alternativ, un programator ar trebui să dispună manual elementele grafice, indicând proprietăți precum poziția și dimensiunea. Adițional, ar trebui să gestioneze explicit actualizarea acestor valori în cazul producerii unor evenimente de tip minimizare sau maximizare a ferestrei în care sunt conținute. === BorderPane === Un container de tip ''BorderPane'' oferă cinci regiuni pentru poziționarea nodurilor: sus, jos, stânga, dreapta şi centru, acestea putând avea orice dimensiune. În situația în care aplicația JavaFX nu are nevoie de o anumită regiune, aceasta nu va fi definită, nefiind alocat spațiu pentru ea. O situație în care un astfel de mecanism al conținutului este oportun de folosit este cazul în care în partea de sus este plasat un meniu, în partea de jos o bară de stare, în stânga un panou de navigare, în dreapta un panou pentru informații suplimentare iar în centru conținutul propriu-zis. Dacă spațiul de afișare este prea mic, regiunile se pot suprapune. Modul în care sunt suprapuse regiunile este determinat de ordinea în care acestea au fost definite. Dacă spațiul de afișare este prea mare, acesta va fi alocat în zona centrală. Pentru un obiect ''BorderPane'' sunt disponibile metodele ''setTop()'', ''setBottom()'', ''setLeft()'', ''setRight()'', ''setCenter()'', toate primind ca parametru un obiect de tip ''Node'' (inclusiv de tip container) al cărui conținut va fi redat în regiunea respectivă. === HBox / VBox === Un container de tip ''HBox'' oferă un mecanism de dispunere a nodurilor într-un singur rând. Un container de tip ''VBox'' oferă un mecanism de dispunere a nodurilor într-o singură coloană. Metoda ''setPadding()'', ce primește ca parametru un obiect de tip ''Insets'', definește distanțele față de cele patru laturi ale continerului la care vor fi dispuse elementele componente. Metoda ''setSpacing()'', ce primește ca parametru o valoare întreagă, specifică distanțele dintre elementele componente. Metoda ''setMagin()'' poate fi utilizată pentru a indica un spațiu suplimentar în jurul unui control grafic. Aceasta primește ca parametri elementul la care se referă și un obiect de tip ''Insets'' care precizează valoarea spațierii pentru fiecare latură în parte. Asocierea unor controale grafice la un obiect ''HBox'' / ''VBox'' se face prin metodele ''add()'' / ''addAll()'' apelată pentru lista observabilă a componentelor sale: HBox hBox = new HBox(); hbox.setPadding(new Insets(5, 5, 5, 5)); hbox.setSpacing(5); Control control1 = new Control(); hBox.getChildren().addAll(control1); // ... VBox vBox = new VBox(); Control control2 = new Control(); vBox.setMargin(control2, new Insets(5, 5, 5, 5)); vBox.getChildren().addAll(control2); === StackPane === Un container de tip ''StackPane'' plasează toate nodurile într-o singură stivă în care un nod nou este așezat peste un nod vechi. Un astfel de mecanism de dispunere poate fi utilizat pentru a suprapune un text peste o imagine sau pentru a suprapune mai multe forme pentru a obține o formă complexă. Metoda ''setAlignment()'' (ce primește ca parametri valori din interfața ''Pos'') indică modul în care componentele sunt poziționate în cadrul containerului, referindu-se la întregul conținut. Locația la care va fi plasat fiecare element grafic poate fi controlat prin intermediul marginilor. === GridPane === Un container de tip ''GridPane'' permite dispunerea elementelor grafice la orice poziție din cadrul unui tabel având un anumit număr de rânduri și coloane, un nod putând să ocupe spațiul corespunzător mai multor celule. Acest mecanism de dispunere a conținutului este util de folosit atunci când sunt proiectate formulare sau pentru orice conținut organizat sub formă de rânduri și coloane. Proprietatea ''gridLinesVisible'' delimitează liniile și coloanele, evidențiind și spațiile dintre acestea, fiind utilă mai ales în partea de testare a aplicației. Spațiile dintre linii și coloane sunt controlate prin metodele ''setHgap()'' și ''setVgap()'', ce primesc ca parametrii valori întregi indicând distanțele propriu-zise. Metoda ''setPadding()'' controlează distanța față de marginile containerului. Asocierea unui element grafic la obiectul de tip ''GridPane'' se face prin metoda ''add'' ce primește ca parametru nodul care va fi inclus, coloana și linia la care va fi poziționat și, în cazul controalelor care ocupă mai multe coloane / linii, numărul acestora. Pentru un obiect ''GridPane'' pot fi specificate constrângeri de aliniere pentru rânduri și coloane, prin intermediul claselor ''RowConstraints'', respectiv ''ColumnConstraints'', care definesc metodele ''setHalignment()'', respectiv ''setValingment()'', având ca parametri valori din interfețele ''HPos'', respectiv ''VPos''. În situația în care fereastra este redimensionată, nodurile din cadrul acestui mecanism de dispunere a conținutului sunt redimensionate în funcție de constrângerile fiecăreia. === FlowPane === Un container de tip ''FlowPane'' sunt dispuse continuu, unul după celălalt, în funcție de orientare (verticală - în coloane, orizontală - în rânduri) și de limitele mecanismului de dispunere (înălțime, respestiv lățime). === TilePane === Un container de tip ''TilePane'' poziționează fiecare element într-o matrice în care fiecare celulă are aceeași dimensiune. Dispunerea se poate face orizontal (în rânduri) sau verical (în coloane) limitându-se la lățimea, respectiv înălțimea mecanismului de dispunere. Dimensiunea unei celule poate fi determinată dacă se specifică numărul preferat de rânduri și se coloane, prin intermediul metodelor ''setPrefRows()'', respectiv ''setPrefColumns()''. === AnchorPane === Un container de tip ''AnchorPane'' permite raportarea poziției unui nod la marginile de sus, jos, din stânga, dreapta sau la centrul mecanismului de dispunere. În situația în care fereastra este redimensionată, elementele grafice își mențin poziția relativ la punctul față de care au fost definite. Poziția unui nod poate fi specificată în funcție de mai multe elemente de referință și un element de referință poate servi drept reper pentru mai multe noduri. Metodele pe care le definește un obiect ''AnchorPane'' sunt ''setTopAnchor()'', ''setBottomAnchor()'', ''setLeftAnchor()'', ''setRightAnchor()'' și ''setCenterAnchor()'', acestea primind ca parametri elementul grafic care este poziționat și distanța față de referință la care este plasat. ==== ==== Dimensiunile unui control grafic sunt în general determinate de conținutul acestuia, astfel încât acesta să fie vizibil în totalitate. Astfel de proprietăți sunt controlate și de containerul în care acestea sunt incluse. Totuși pentru fiecare control sunt definite: * dimensiuni minime, controlate prin metodele: * ''setMinWidth()'' / ''setMinHeight()'' * ''setMinSize()'' * dimensiuni maxime, controlate prin metodele: * ''setMaxWidth()'' / ''setMaxHeight()'' * ''setMaxSize()'' * dimensiuni preferate, controlate prin metodele ''setPrefWidth()'' / ''setPrefHeight()'' În cazul în care se dorește ca un element grafic să sibă valori constante indiferent de modificarea dimensiunii containerului din care face parte, se va specifica valoarea ''Control.USE_PREF_SIZE'' atât ca valoare minimă cât și ca valoare maximă, precizând eventual și o valoare explicită pentru valoarea preferată. Implicit, valoarea preferată este cea calculată, pe baza conținutului elementului grafic și a constrângerilor containerului din care face parte. În situația în care se dorește ca un element grafic să nu fie limitat, fiind redimensionat automat la întregul spațiu disponibil pe care îl are la dispoziție, se va specifica valoarea ''Double.MAX_VALUE'' ca valoare maximă. Alinierea controalelor se face prin intermediul metodei ''setAlignment()'' aplicată pentru obiectul de tip container din care fac parte, acesta putând lua valori: * precizate de interfața ''VPos'' pentru aliniere verticală; * precizate de interfața ''HPos'' pentru aliniere orizontală; * precizate de interfața ''Pos'' pentru aliniere verticală și orizontală (valorile sunt separate prin caracterul ''_'' astfel: în stânga sa este precizat tipul de aliniere verticală, în dreapta sa este indicat tipul de aliniere orizontală); centrarea pe verticală și orizontală se face prin valoarea ''Pos.CENTER''. ==== Foi de Stil ==== O foaie de stil (//eng.// CSS - Cascading Style Sheet) conține definiții cu privire la aspectul elementelor de interfață cu utilizatorul. Utilizarea foilor de stil în aplicaţiile JavaFX este similară ca în cazul paginilor Internet HTML întrucât este utilizată aceeași specificație ([[http://www.w3.org/TR/CSS21/|versiunea 2.1 a W3C CSS]] cu unele actualizări din versiunea 3). Implicit, orice aplicație JavaFX folosește foaia de stil ''modena.css'', disponibilă în arhiva ''jfxrt.jar'' (din ''$JAVA_HOME/jre/lib/ext'') - în directorul ''com/sun/javafx/scene/control/skin/modena/''. **1.** Pentru fiecare obiect instanţă a unui control se poate apela metoda ''setStyle()'' primind ca parametru un şir de caractere de forma ''atribut:valoare'', separate prin '';'', unde atribut este denumirea elementului de stil (prefixat cu ''-fx-''), iar valoare reprezintă caracteristica asociată, respectând o semantică specifică. **2.** Un fişier ''.css'' care specifică formatul asociat pentru diferite elemente este asociat unei scene prin metoda ''scene.getStyleSheets().add("custom_style.css")''. O definiție a unui stil constă din denumirea acestuia (denumită selector sau clasă de stil, prefixată de regulă de caracterul ''.'') și o serie de reguli incluse între acolade (''{}'') care indică proprietățile respective. .custom-control { -fx-font: 12px "Arial"; -fx-padding: 5; -fx-background-color: beige; } Dimensiunea pentru caractere poate fi specificată fie în puncte (pt), fie în pixeli (px). Se presupune că rezoluția este de 96 dpi (dots per inch), astfel că 1px = 0.75pt. Dacă se dorește specificarea unui stil pentru un anumit control, în locul clasei de stil poate fi specificat identificatorul elementului grafic respectiv (dat prin metoda ''setId()''), prefixat de caracterul ''#''. În situația în care un control poate avea mai multe stări și se dorește specificarea unor clase de stil pentru fiecare dintre acestea, clasa de stil va avea forma ''denumire element grafic:stare element grafic''. Folosirea unei clase de stil pentru un element grafic se face prin metoda ''add()'' apelată pentru clasa de stil a obiectului respectiv, primind ca parametru denumirea indicată în fișierul ''.css'': customControl.getStyleClass().add("custom-control"); ==== Gestiunea Evenimentelor ==== Evenimentele reprezintă mecanisme utilizate pentru a transmite aplicaţiei acţiunile realizate de utilizator, astfel încât aceasta să reacţioneze corespunzător. În JavaFX, un eveniment e o instanţă a clasei ''javafx.event.Event'' sau a oricărei subclase a acesteia. Pe lângă tipurile predefinite de evenimente (''KeyEvent'', ''MouseEvent'', ''TouchEvent'', ''ScrollEvent'', ''DragEvent'', ''DropEvent''), programatorul îşi poate defini propriile tipuri de evenimente (derivate din clasa ''Event''). |< 100% >| ^ **Proprietate** ^ **Descriere** ^ | tip eveniment | categoria evenimentului care s-a produs | | sursă | originea evenimentului, raportat la locaţia evenimentului în lanţul de propagare | | ţintă | nodul asupra căruia a avut loc acţiunea şi nodul frunză în lanţul de propagare | Sursa se modifică pe măsură ce evenimentul este transmis de-a lungul lanţului de propagare. Deşi ţinta nu se schimbă, dacă un filtru pentru evenimente consumă evenimentul în procesul de tratare a avenimentului, ţinta nu va primi evenimentul. Un **tip de eveniment** este o instanţă a clasei ''EventType'', acestea fiind organizate ierarhic, pe nivelul cel mai înalt aflându-se ''Event.ROOT'' echivalent cu ''Event.ANY''. Pe fiecare nivel, subtipul ''ANY'' este folosit pentru a desemna orice tip de eveniment din clasă. Mecanismul este foarte util în cazul în care se folosesc filtre pentru evenimente, putându-se trata fie doar anumite tipuri de evenimente, fie toate tipurile de evenimente specifice clasei respective. {{ :laboratoare:laborator09:07event_type_hierarchy.png?nolink }} **Sursa** unui eveniment desemnează nodul care are asociat mecanismul de tratare a evenimentului, aflându-se cel mai aproape de nodul propriu-zis (asupra căruia s-a exercitat acţiunea respectivă) în ierarhia de obiecte a scenei. **Ţinta** unui eveniment este instanţa clasei ''EventTarget'' sau a oricărei subclase a acesteia. Lanţul de transmitere al evenimentului (ruta pe care evenimentul o urmează pentru a ajunge la ţintă) este realizată prin metoda ''buildEventDispatchChain()''. Clasele ''Window'', ''Scene'' şi ''Node'' implementează interfaţa ''EventTarget'' şi toate subclasele lor moştenesc această proprietate, ceea ce înseamnă că pentru majoritatea obiectelor nu va fi necesar să se stabilească lanţul de transmitere a evenimentului, implementându-se doar modul de reacţie la evenimentul respectiv. Pentru controalele definite de utilizator, în cazul în care nu sunt subclase ale ''Window'', ''Scene'' şi ''Node'', va trebui să se implementeze interfaţa ''EventTarget'', pentru ca acesta să gestioneze acţiunile utilizatorului. Prin **lanţ de propagare** a unui eveniment se înţelege un proces format din următoarele etape: selecţia ţintei, stabilirea parcursului urmat de eveniment, capturarea propriu-zisă evenimentului şi întoarcerea sa la nodul rădăcină. În etapa de //selecţie a ţintei//, se determină care este nodul ţintă, în funcţie de următoarele reguli: * în evenimente legate de tastatură, nodul ţintă este cel ce deţine controlul la momentul respectiv; * în evenimente legate de mouse, nodul ţintă este cel de la locaţia cursorului; pentru evenimente legate de mouse sintetizate, punctul de atingere este considerat locația cursorului; * în evenimente ce implică gesturi în cazul dispozitivelor cu ecran tactil, nodul ţintă este cel situat în centrul acţiunilor utilizatorului, la începutul gestului respectiv; în cazul unor evenimente ce implică gesturi realizate pe un dispozitiv ce nu conţine ecran tactil, nodul ţintă este cel de la locaţia cursorului; * în evenimente de tip glisare în cazul dispozitivelor cu ecran tactil, nodul ţintă este cel situat în centrul acţiunilor utilizatorului, considerându-se întregul parcurs al acestuia; în cazul unor evenimente ce implică gesturi realizate pe un dispozitiv ce nu conţine ecran tactil, nodul ţintă este cel de la locaţia cursorului; * în evenimente de tip atingere în cazul dispozitivelor cu ecran tactil, nodul ţintă pentru fiecare punct este cel de la locaţia primei apăsări; se poate specifica un alt nod ţintă folosind metodele ''grab(node)'' sau ''ungrab()'' pentru un anumit punct într-un filtru de evenimente sau mecanism de tratare a evenimentelor. În cazul în care la locaţia evenimentului se află mai multe noduri, ţinta este cea de la nivelul cel mai înalt. De asemenea, când se produce un eveniment cum ar fi o apăsare la nivel de tastatură sau de mouse, evenimentele succesive (până ce tasta sau butonul respectiv sunt eliberate) vor fi legate de nodul ţintă iniţial. Pentru //stabilirea parcursului urmat de eveniment//, în principiu se foloseşte lanţul de propagare a evenimentului dat de metoda ''buildEventDispatchChain()'' de la nivelul nodului ţintă selectat (aceasta va conţine în mod obligatoriu obiectele ''Stage'' şi ''Scene'' care conţin nodul respectiv şi apoi calea propriu-zisă către nodul respectiv în graful de scene). Totuşi, ruta poate fi modificată după cum filtrele de evenimente respectiv metodele de tratare pentru evenimente de pe parcursul urmat de eveniment îl procesează (spre exemplu, dacă evenimentul este "consumat" înainte de a ajunge la nodul ţintă). În etapa de //capturare propriu-zisă// (//eng.// event capturing phase), evenimentul în cauză este transmis de la nodul rădăcină către nodul ţintă prin lanţul de propagare a evenimentului. Fiecare nod din acest lanţ de propagare al evenimentului care are implementată o metodă pentru respectivul tip de eveniment îl va gestiona, ulterior transmiţându-l mai departe. Dacă pe parcurs nu există un filtru pentru eveniment care să îl consume, acesta va ajunge la nodul ţintă într-un final. După ce evenimentul a ajuns la nodul ţintă şi / sau a fost procesat de către toate filtrele, acesta se va //întoarce la nodul rădăcină// (//eng.// event bubbling phase), urmărind parcursul dat de lanţul de propagare, însă în sens invers. Dacă vreun nod în lanţul de propagare are definită o metodă de tratare pentru respectivul tip de eveniment, aceasta este apelată, transmiţând evenimentul mai departe după ce se termină. Evenimentul va fi transmis într-un final şi nodului rădăcină doar dacă metodele de tratare a acestuia nu îl consumă. {{ :laboratoare:laborator09:08event_managing.png?nolink&600 }} În JavaFX, gestiunea evenimentelor se face prin **filtre** şi **metode de tratare**, acestea fiind implementări ale interfeţei ''EventHandler''. Atunci când se dorește ca aplicaţia să fie notificată de apariţia unui eveniment, trebuie asociat fie un filtru, fie o metodă de tratare specifică evenimentului respectiv. Diferenţa dintre un filtru şi o metodă de tratare constă în momentul la care fiecare dintre acestea sunt executate. Un **filtru** este executat în etapa de capturare propriu-zisă. La nivelul unui nod părinte, pot fi implementate filtre pentru mai multe noduri fiu şi în caz de necesitate, evenimentul poate fi consumat spre a preveni ca acesta să mai ajungă la nodul ţintă. Un nod poate avea asociate mai mult de un singur filtru. Ordinea în care acestea sunt executate ţine de ierarhia tipurilor de evenimente. Filtrele asociate unui tip de eveniment mai specific sunt executate înainte de filtrele asociate unui tip de eveniment mai generic. Nu se specifică însă ordinea în care sunt executate filtrele corespunzătoare unor tipuri de evenimente aflate pe acelaşi nivel. O **metodă de tratare** este executată în etapa de întoarcere la nodul rădăcină. Dacă metoda de tratare de la nivelul nodului fiu nu consumă evenimentul, atunci nodul părinte îl poate gestiona ulterior. Şi în cazul metodelor de tratare se aplică aceeaşi regulă din cazul filtrelor cu privire la numărul de evenimente pe care acestea îl pot gestiona precum şi a ordinii în care acestea sunt executate. Evenimentele specificate prin metode de oportunitate (//eng.// convenience methods) se execută ultimele. Un eveniment poate fi consumat de un filtru sau de o metodă de tratare oricând în lanţul de propagare al evenimentelor prin metoda ''consume()'' care anunţă faptul că procesarea evenimentului s-a încheiat. Consumarea unui eveniment de un filtru împiedică nodurile fii să îl primească la fel cum consumarea sa de către o metodă de tratare împiedică nodurile părinte să îl primească. Nu este afectată însă gestiunea altor tipuri de evenimente din cadrul nodului respectiv. Unele clase JavaFX definesc proprietăţi pentru metodele de tratare, oferind un mecanism de a specifica un comportament în cazul în care se produc anumite evenimente. Acestea poartă numele de //mecanisme de oportunitate// pentru asocierea unor metode de tratare. Acestea sunt definite în clasa ''Node'', fiind disponibile pentru toate subclasele lor. Alte clase işi au definite propriile mecanisme de oportunitate. ^ **Acţiunea Utilizatorului** ^ **Tip de Eveniment** ^ **Clasa** ^ | Se apasă o tastă de pe tastatură. | ''KeyEvent'' | ''Node'',\\ ''Scene'' | | Mouse-ul este mişcat sau se apasă un buton de pe mouse. | ''MouseEvent'' | ''Node'',\\ ''Scene'' | | Se produce o secvenţă de acţiuni care implică apăsarea unui buton de pe mouse, mişcarea mouse-ului urmată de eliberarea butonului. | ''MouseDragEvent'' | ''Node'',\\ ''Scene'' | | Se transmite conţinut printr-o metodă alternativă de introducere a caracterelor (limbă ce utilizează alte tipuri de caractere) | ''InputMethodEvent'' | ''Node'',\\ ''Scene'' | | Un obiect este mutat între locaţii prin selectarea, transportarea şi deselectarea sa. | ''DragEvent'' | ''Node'',\\ ''Scene'' | | Obiectul este derulat. | ''ScrollEvent'' | ''Node'',\\ ''Scene'' | | Se produce un gest de tip rotaţie asupra obiectului. | ''RotateEvent'' | ''Node'',\\ ''Scene'' | | Se produce un eveniment de tip derulare asupra unui obiect. | ''SwipeEvent'' | ''Node'',\\ ''Scene'' | | Pe ecranul tactil, este atinsă locaţia la care se află un obiect. | ''TouchEvent'' | ''Node'',\\ ''Scene'' | | Se produce un gest ce implică modificarea rezoluţiei asupra obiectului respectiv. | ''ZoomEvent'' | ''Node'',\\ ''Scene'' | | Este solicitată vizualizarea meniului contextual. | ''ContextMenuEvent'' | ''Node'',\\ ''Scene'' | | Butonul este apăsat.\\ Lista derulantă este vizibilă sau este ascunsă.\\ Elementul unui meniu este selectat. | ''ActionEvent'' | ''ButtonBase'',\\ ''ComboBoxBase'',\\ ''ContextMenu'',\\ ''MenuItem'',\\ ''TextField'' | | Se editează elementele dintr-un obiect de tip listă, tabel sau arbore. | ''ListView.EditEvent''\\ ''TableColumn.CellEditEvent''\\ ''TreeView.EditEvent'' | ''ListView'',\\ ''TableColumn'',\\ ''TreeView'' | | Obiectul de redare a conţinutului multimedia întâlneşte o eroare. | ''MediaErrorEvent'' | ''MediaView'' | | Meniul este vizibil sau ascuns. | ''Event'' | ''Menu'' | | O fereastră pop-up este ascunsă. | ''Event'' | ''PopupWindow'' | | Panoul este închis sau selectat. | ''Event'' | ''Tab'' | | Fereastra e închisă, vizibilă, ascunsă | ''WindowEvent'' | ''Window'' | O metodă de tratare e asociată nodului prin mecanismul de oportunitate folosind următoarea sintaxă: setOnEvent-type(EventHandler value) unde: * ''event-type'' este tipul evenimentului pe care îl gestionează metoda de tratare; * ''event-class'' este clasa care defineşte tipul de eveniment (sintaxa '''' indică faptul că metoda acceptă o metodă de tratare fie pentru clasa care defineşte tipul de eveniment (event-class), fie pentru una dintre superclasele acesteia). Totodată, se poate implementa metoda de tratare prin definirea acesteia într-o clasă anonimă în cadrul mecanismului de oportunitate, specificându-se comportamentul în cazul producerii evenimentului în contextul funcţiei ''handle()''. tableContent.setOnMouseClicked(new EventHandler() { @Override public void handle(MouseEvent event) { // ... } }); tableContent.setOnMouseClicked( (MouseEvent event) -> { // ... } ); Eliminarea metodei de tratare a evenimentului se face prin specificarea unui parametru cu valoarea ''null'' pentru nodul în cauză, dacă se doreşte ca acesta să nu mai proceseze astfel de evenimente. tableContent.setOnMouseClicked(null); {{ :laboratoare:laborator09:09observer_listener.png?nolink&600 }} Un filtru de eveniment permite gestiunea evenimentului în timpul etapei de capturare propriu-zisă. Un nod poate avea asociate unul sau mai multe filtre pentru gestiunea unui eveniment. De asemenea un filtru poate fi utilizat pentru unul sau mai multe noduri, pentru unul sau mai multe tipuri de evenimente. Totodată, nodurile părinte pot oferi o procesare de nivel înalt a evenimentului pentru nodurile fiu, interceptându-le astfel ca acestea să nu mai ajungă la ţintă. Pentru a gestiona un eveniment în timpul etapei de capturare propriu-zisă, nodul trebuie sa asocieze un filtru de eveniment, care implementează interfaţa ''EventHandler''. Metoda ''handle()'' conţine codul executat la momentul în care evenimentul ajunge la nodul care are filtrul asociat. Asocierea unui filtru de eveniment pentru un nod se face prin metoda ''addEventFilter()'', primind ca parametri tipul de eveniment şi filtrul propriu-zis. Eliminarea unui filtru de eveniment corespunzător unui nod se face prin metoda ''removeEventFilter()'', primind aceeaşi parametri. node.addEventFilter(MouseEvent.MOUSE_CLICKED, new EventHandler() { @Override public void handle(MouseEvent) { ... }; }); EventHandler filter = new EventHandler(() { @Override public void handle(InputEvent event) { // ... event.consume(); } node1.addEventFilter(MouseEvent.MOUSE_PRESSED, filter); node2.addEventFilter(MouseEvent.MOUSE_PRESSED, filter); node.addEventFilter(KeyEvent.KEY_PRESSED, filter); // ... node.removeEventFilter(KeyEvent.KEY_PRESSED, filter); De remarcat faptul că filtrul este definit pentru un tip de eveniment generic (''InputEvent''), fiind utilizat apoi pentru tratarea unor evenimente specifice (legate de mouse şi de tastatură). O metodă de tratare a unui eveniment permite gestiunea evenimentului în timpul etapei de întoarcere la nodul rădăcină. Un nod poate avea asociată una sau mai multe metode pentru tratare a unui eveniment. De asemenea, o metodă pentru tratarea unui eveniment poate fi folosită pentru unul sau mai multe noduri, respectiv unul sau mai multe tipuri de eveniment. În cazul când o metodă de tratare a unui eveniment de la nivelul nodului fiu nu consumă evenimentul, acesta va fi gestionat şi de metoda de tratare de la nivelul nodului părinte, oferind un mecanism de gestiune mai general caracteristic tuturor nodurilor fiu. Pentru a gestiona un eveniment în timpul etapei de întoarcere la nodul rădăcină, nodul trebuie sa asocieze o metodă de tratare, care implementează interfaţa EventHandler. Metoda ''handle()'' conţine codul executat la momentul în care evenimentul ajunge la nodul care are metoda de tratare asociată. Asocierea unei metode de tratare pentru un nod se face prin metoda ''addEventHandler()'', primind ca parametri tipul de eveniment şi metoda de tratare propriu-zis. Eliminarea unei metode de tratare corespunzătoare unui nod se face prin metoda ''removeEventHandler()'', primind aceeaşi parametri. node.addEventHandler(DragEvent.DRAG_ENTERED, new EventHandler() { @Override public void handle(DragEvent) { ... }; }); EventHandler handler = new EventHandler() { @Override public void handle(InputEvent event) { // ... event.consume(); } node1.addEventHandler(DragEvent.DRAG_EXITED, handler); node2.addEventHandler(DragEvent.DRAG_EXITED, handler); node.addEventHandler(MouseEvent.MOUSE_DRAGGED, handler); node.removeEventHandler(DragEvent.DRAG_EXITED, handler); Şi în acest caz metoda de tratare este definită pentru un tip de eveniment generic urmând a fi folosită pentru tratarea unor evenimente specifice. Pentru anumite tipuri de dispozitive mobile, pot fi detectate evenimente de tipul atingerilor, operaţiilor de micşorare/mărire (//eng.// zoom), rotire, derulare (//eng.// scroll) sau glisare (//eng.// swipe). Aceste tipuri de gesturi pot implica unul sau mai multe puncte de contact. Tipul de eveniment generat corespunde gestului pe care îl realizează utilizatorul. ^ **Gest** ^ **Descriere** ^ **Evenimente generate** ^ | Rotaţie | Mişcarea unui deget în jurul altuia în sensul acelor de ceasornic sau în sens contrar acelor de ceasornic | ''ROTATION_STARTED''\\ ''ROTATE''\\ ''ROTATION_FINISHED'' | | Derulare | Mişcare de derulare, pe verticală (sus/jos) sau pe orizontală (stânga/dreapta) | ''SCROLL_STARTED''\\ ''SCROLL''\\ ''SCROLL_FINISHED''\\ Dacă se foloseşte rotiţa de mouse se generează doar evenimentul ''SCROLL''. | | Glisare | Mişcare de glisare de-a lungul ecranului la stânga, dreapta, în sus sau în jos. Mişcarea pe diagonală nu este recunoscută ca glisare. | ''SWIPE_LEFT'', ''SWIPE_RIGHT'',\\ ''SWIPE_UP'', ''SWIPE_DOWN''\\ Pentru fiecare gest se generează un singur eveniment de parcurgere. | | Mărire/Micşoare | Depărtarea a două degete pentru mărire, respectiv apropierea lor pentru micşorare. | ''ZOOM_STARTED''\\ ''ZOOM''\\ ''ZOOM_FINISHED'' | Aşa cum s-a precizat, ţinta celor mai multe gesturi este reprezentată de nodul aflat în centrul tuturor punctelor de apăsare de la începutul gestului respectiv. În situaţia în care există mai mult de un nod aflat la poziţia respectivă, ţinta este considerată nodul aflat deasupra celorlalte. Unele gesturi pot genera şi alte tipuri de evenimente în plus faţă de cele care erau intenţionate. Operaţiile de glisare pot genera operaţii de derulare în funcţie de amploarea gestului. Totodată, operaţiile de atingere generează atât evenimente de tip ''TOUCH_PRESSED'' / ''TOUCH_RELEASED'' cât şi evenimente de tip ''MOUSE_PRESSED'' / ''MOUSE_RELEASED''. În aceste cazuri, este posibil ca nodurile ţintă asociate celor două evenimente să fie diferite. De asemenea, în acest mod, aplicaţiile desktop pot fi portate foarte uşor pe sisteme încorporate ce utilizează ecranul tactil. Pentru ca acelaşi eveniment să nu fie procesat de două ori, trebuie apelată metoda ''isSynthesized()'' pentru a se verifica dacă evenimentele nu au fost generate din alte evenimente. În acelaşi scop poate fi folosită metoda ''isDirect()'' care indică faptul că evenimentul a fost generat de un gest realizat pe ecranul tactil. Pentru a verifica dacă anumite evenimente nu s-au generat ca urmare a inerţiei unui gest se poate apela metoda ''isInertia()''. În cazul în care se doreşte ca evenimentele de apăsare a ecranului să fie tratate individual (sau se doreşte implementarea de alte gesturi) pot fi utilizate evenimentele puse la dispoziţie de clasa ''TouchEvent'', ce pot avea tipurile ''TOUCH_PRESSED'', ''TOUCH_MOVED'', ''TOUCH_STATIONARY'' şi ''TOUCH_RELEASED''. Pentru fiecare apăsare a ecranului (punct de contact) se poate genera un eveniment având acest tip. Un punct de contact este reprezentat de o instanţă a clasei ''TouchPoint'' conţinând informaţii despre locaţie, stare şi nodul ţintă al punctului de contact. În cazul apăsărilor concomitente ale ecranului tactil, numărul de evenimente generate poate fi limitat de platforma pe care rulează aplicaţia. Un eveniment de apăsare are asociate următoarele elemente: * punctul de contact – reprezintă punctul principal de contact asociat evenimentului respectiv; * numărul de apăsări – numărul punctelor de contact legate de acţiunea de apăsare; * lista punctelor de contact – mulţimea punctelor de contact asociate evenimentului de apăsare în cauză; * identificatorul mulţimii de evenimente – identificatorul mulţimii ce conţine evenimentul de apăsare; fiecare set de evenimente are un identificator unic care este incrementat pentru fiecare mulţime care este generată ca urmare a unei operaţii de apăsare; evenimentele din mulţime pot avea tipuri diferite sau acelaşi tip astfel că fiecare punct de contact este identificat şi el printr-un identificator unic. Nodul ţintă al unui eveniment de apăsare corespunde cu nodul ţintă al punctului de contact, reprezentând controlul cel mai vizibil (de pe nivelul frunză al grafului de scene) de la coordonatele respective. Toate evenimentele asociate unui punct de contact sunt transmise aceluiaşi nod ţintă. Totuşi, un nod care procesează un eveniment poate apela metoda ''grab()'' pentru a deveni ţinta respectivului punct de contact , sau poate specifica această ţintă ca parametru al metodei respective: ''grab(node)''. De asemenea, metoda ''ungrab()'' este folosită pentru a detaşa punctul de contact de ţinta curentă, evenimentele asociate fiind transmise nodului corespunzător locaţiei la care se află punctul de contact. În acest fel, există posibilitatea ca toate evenimentele aparţinând unei anumite mulţimi să fie tratate de acelaşi nod ţintă. JavaFX oferă suport şi pentru operaţii de tip drag-and-drop, ce implică un transfer de date între două obiecte: sursa gestului şi ţinta gestului, acestea putând fi instanțe ale obiectelor de tip ''Node'' şi ''Scene'', putând aparţine aceleiaşi aplicaţii sau unor aplicaţii diferite (una dintre aplicaţii poate să nu fie aplicaţie JavaFX ci o aplicaţie a sistemului de operare). O astfel de operaţie este implementată folosind clasa ''javafx.scene.input.DragEvent''. Modurile de transfer suportate între sursă şi ţintă sunt ''COPY'', ''MOVE'' şi ''LINK''. Gestul de tip drag and drop poate fi pornit doar din cadrul unui nod sursă prin apelarea metodei ''startDragAndDrop()'' care primeşte ca argument tipurile de transfer pe care aceasta le suportă, în cadrul metodei de tratare a evenimentului ''DRAG_DETECTED''. Pentru a specifica toate modurile de transfer, se va indica parametrul ''TransferMode.ANY''. Acceptarea unei operaţii se face în cadrul nodului ţintă prin metoda ''acceptTransferModes()'' în cadrul metodei de tratare a avenimentului ''DRAG_OVER'' prin specificarea tipurilor de transfer pe care le acceptă. De regulă, se verifică tipul de date pe care îl conţine obiectul pentru care se doreşte să se realizeze operaţia de transfer, conţinut ce poate fi obţinut prin metoda ''getDragboard()''. Trecerea obiectului pentru care se încearcă operaţia drag and drop deasupra anumitor noduri generează evenimente de tip ''DRAG_ENTERED'' sau ''DRAG_EXITED''. Astfel, pot fi definite metode de tratare ale acestor evenimente care să schimbe aspectul acestui tip de obiecte pentru a indica faptul că nodul respectiv acceptă sau respinge transferul în cauză. Pe lângă conţinutul obiectului se poate verifica şi sursa acestuia prin metoda ''getGestureSource()''. În momentul în care obiectul este transferat asupra ţintei, aceasta primeşte un eveniment de tip ''DRAG_DROPPED'' în care pot fi preluate informaţiile pe care le conţine marcând rezultatul operaţiei prin metoda ''setDropCompleted()'' care primeşte un argument de tip ''boolean''. Când gestul drag and drop este terminat, sursa primeşte un eveniment ''DRAG_DONE'' care poate fi tratat în sensul că pentru anumite moduri de transfer trebuie realizate operaţii suplimentare . JavaFX permite şi transferul de controale definite de utilizator însă în acest caz tipul de date trebuie să fie specificat explicit şi trebuie să fie serializabil. ==== Conținut Multimedia ==== === Efecte Vizuale === JavaFX definește mai mult efecte vizuale în pachetul ''javafx.scene.effect'', derivate din clasa ''Effect''. == Blend == Efectul de combinare (//eng.// blend) realizează un amestec între mai multe surse, folosind una dintre modalitățile predefinite pentru realizarea acestei operații: ''SRC_ATOP'', ''MULTIPLY'', ''SRC_OVER'' (din interfața ''BlendMode''). În cazul în care metoda ''setBlendMode()'' este apelată pentru un nod, elementele care sunt îmbinate sunt nodul care este redat (intrarea de nivel superior) și toate elementele care se găsesc sub acesta (intrarea de nivel inferior). Delimitarea intrării de nivel inferior se bazează pe mai multe reguli: * sunt incluse toate elementele inferioare (folosind ordinea Z); * dacă grupul are definită o modalitate de combinare, procesul este terminat; * dacă grupul are modalitatea de combinare implicită, toate elementele aflate sub grup sunt incluse recursiv, folosind aceleași reguli; * dacă s-a ajuns la nodul rădăcină, se include fundalul scenei. În situația în care fundalul scenei, care este de regulă o culoare opacă, este inclus în intrarea de nivel inferior, modalitățile de combinare ''SRC_ATOP'' și ''SRC_OVER'' sunt echivalente și nu au nici un fel de efect. == Bloom == Efectul de înflorire (//eng.// bloom) face ca porțiunile de imagine mai deschise să pară că strălucesc. De regulă, se definește un prag, cuprins între 0.0 și 1.0, acesta fiind transmis ca parametru al metodei ''setThreshold()'' a unui obiect de tip ''Bloom''. Un astfel de efect este asociat unui nod prin intermediul metodei ''setEffect()''. == Blur == Efectul de estompare (//eng.// blur) este utilizat pentru a capta atenția asupra anumitor obiecte. În JavaFX pot fi aplicate efecte de tip: * ''BoxBlur'' - efect de estompare ce folosește un nucleu de filtrare de tip cutie, cu valori configurabile independent ale ambelor dimensiuni pentru a controla cantitatea de estompare aplicată unui obiect precum și un parametru ''Iterations'' care gestionează calitatea estompării obținute; aceste valori sunt specificate prin metodele ''setWidth()'', ''setHeight()'', respectiv ''setIterations()''; * ''MotionBlur'' - efect de estompare de tip Gauss, oferind posibilitatea de configurare a razei și a unghiului pentru a crea senzația de obiect în mișcare; aceste valori sunt specificate prin metodele ''setRadius()'', respectiv ''setAngle()''; * ''GaussianBlur'' - efect de estompare pe baza unui algoritm Gauss, cu posibilitatea de configurare a razei; această valoare poate fi specificată prin metoda ''setRadius()'' dar poate fi și omisă; Toate efectele de tip estompare pot fi asociat unui nod prin intermediul metodei ''setEffect()''. == Drop Shadow == Un efect de tip umbră exterioară (//eng.// drop shadow) realizeză o umbră a conținutului pe care este aplicat. Este permisă specificarea culorii, razei, distanței (pe ambele axe de coordonate) precum și a altor parametri, prin metodele ''setColor()'', ''setRadius()'', ''setOffsetX()'', ''setOffsetY()''. Culoarea asociată efectului de tip umbră exterioară trebuie să fie mai deschisă cu câteva nuanțe decât culoarea de fundal. În cazul în care există mai multe obiecte în cadrul scenei asupra cărora s-a aplicat un efect de tip umbră exterioară, este recomandat ca pentru toate să se folosească aceeași orientare, pentru a da impresia unei surse de lumină ce provine dintr-o unică direcție. == Inner Shadow == Un efect de tip umbră interioară (//eng.// inner shadow) realizează o umbră înăuntrul marginilor conținutului pe care este aplicat. Este permisă specificarea culorii, razei, distanței (pe ambele axe de coordonate) precum și a altor parametri, prin metodele ''setColor()'', ''setRadius()'', ''setOffsetX()'', ''setOffsetY()'' == Reflection == Efectul de tip reflexie (//eng.// reflection) redă o copie reflectată a conținutului pe care este aplicat, plasând-o sub acesta. Proprietățile unui astfel de efect sunt opacitatea superioară și inferioară, fracția de reflexie și distanța la care este redată, acestea putând fi specificate prin metodele ''setTopOpacity()'', ''setBottomOpacity()'', ''setFraction()'', ''setTopOffset()''. == Lightning == Efectul de iluminare (//eng.// lightning) simulează o sursă de lumină care acționează asupra conținutului pe care este aplicat pentru a da obiectelor plate o aparență mai realistică, tridimensională. Configurarea unui astfel de efect implică specificarea: * sursa de lumină, prin metoda ''setLight()'' ce primește ca parametru un obiect de tip ''Light'' * ''Light.Distant'' - sursă de lumină distantă; * ''Light.Point'' - sursă de lumină având o poziție specificată prin coordonatele (x, y, z); * ''Light.Spot'' - sursă de lumină având o poziție specificată prin coordonatele (x, y, z), o direcție și un focus; * suprafața de scalare, prin metoda ''setSurfaceScale()''; * constanta de difuzie, prin metoda ''setDiffuseConstant()''; * constanta și exponentul de reflexie, prin metodele ''setSpecularConstant()'', respectiv ''setSpecularExponent()''. == Perspective == Efectul de perspectivă (//eng.// perspective) transformă un obiect bidimensional în spațiul tridimensional, realizând o transformare între două dreptunghiuri, menținând proprietatea de linii drepte a laturilor, fără a păstra și paralelismul acestora. Parametrii ce trebuie specificați în cazul unei astfel de transformări sunt coordonatele (x, y) pentru cele patru colțuri: * stânga jos - metodele ''setLlx()'', ''setLly()''; * dreapta jos - metodele ''set Lrx()'', ''setLry()''; * stânga sus - metodele ''setUlx()'', ''setUly()''; * dreapta sus - metodele ''setUrx()'', ''setUry()''. Efectul nu actualizează coordonatele pentru evenimentele asociate unui astfel de control grafic, comportamentul nefiind precizat într-o astfel de situație. == Înlănțuirea Efectelor == Unele efecte dispun de proprietatea ''inputProperty'' care poate fi utilizată pentru realizarea unui lanț de efecte. Astfel, metoda ''setInput()'' primește ca parametru un alt efect care va fi realizat înaintea aplicării propriu-zise a efectului pentru care se apelează. Un lanț de efecte are o structură arborescentă, întrucât unele efecte au două intrări în timp ce alte efecte nu au nici o intrare. === Transformări 2D / 3D === JavaFX permite realizarea de transformări, acestea fiind conținute în pachetul ''javafx.scene.transform'', ele fiind derivate din clasa ''Transform'' și din subclasa acesteia ''Affine'', care definește conceptul de transformări înrudite. Acestea sunt bazate pe algebra euclidiană și realizează o asociere liniară (prin utilizarea unor matrici) între două sisteme de coordonate, menținând caractere ale liniilor cum ar fi proprietatea de a fi drepte și paralelismul. O transformare schimbă locația unui element grafic într-un sistem de coordonate în funcție de anumiți parametri. În JavaFX, sunt definite mai multe tipuri de transformări, ce pot fi aplicate unui singur nod sau unui grup de noduri, astfel de operații putând fi realizate independent sau grupate împreună: * translatări * rotiri * scalări * tăieri Transformările pot fi realizate de-a lungul celor trei coordonate, oferind posibilitatea de a crea transformări 3D. În acest scop, trebuie folosită și camera de perspectivă. Un obiect 3D poate fi redat prin tehnica Z-buffering care asigură o perspectivă cât mai apropată de realitate: un obiect solid aflat în prim plan blochează elementele din spatele său. O astfel de proprietate poate fi gestionată prin intermediul metodei ''setDepthTest()'' care primește ca parametri valori din interfața ''DepthTest'': ''ENABLE'' și ''DISABLE''. == Translatarea == O transformare de tip translatare mută un nod dintr-o locație în alta de-a lungul axelor relativ la poziția sa. Metodele ce trebuie apelate pe un obiect de tip ''Translate''pentru a realiza o translatare de-a lungul axelor sunt: ''setX()'', ''setY()'', ''setZ()'', acestea primind ca parametri valori reale exprimând cantitatea cu care se modifică poziția curentă, de-a lungul fiecărei coordonate. Alternativ, pentru obiectul de tip element grafic se pot apela metodele ''setTranslateX()'', ''setTranslateY()'' și ''setTranslateZ()''. == Rotația == O transformare de tip rotație mută un nod în jurul unui punct considerat pivot al scenei. Metodele ce trebuie apelate pe un obiect de tip ''Rotate'' pentru a realiza o rotație sunt: * ''setAxis()'', ce primește ca parametru axa din sistemul de coordonate în jurul căreia se va realiza rotația (''Rotate.X_AXIS'', ''Rotate.Y_AXIS'', ''Rotate.Z_AXIS''); * ''setAngle()'', ce primește o valoare reală indicând unghiul cu care se va reaiza rotația; * ''setPivotX()'', ''setPivotY()'', ''setPivotZ()'', indicând coordonatele punctului pivot. == Scalarea == O transformare de tip scalare face ca un nod să apară mai mic sau mai mare, în funcție de factorul de scalare. Printr-o astfel de transformare, dimensiunile de-a lungul axelor sunt multiplicate cu factorul de scalare. Operația de scalare se realizează în jurul unui punct considerat pivot. Metodele ce trebuie apelate pe un obiect de tip ''Scale'' pentru a realiza o scalare sunt: ''setX()'', ''setY()'' și ''setZ()'' ce indică factorii de multiplicare de-a lungul celor trei axe de coordonate. == Tăierea == O transformare de tip tăiere rotește o axă astfel încât axele Ox și Oy să nu mai fie perpendiculare, valorile coordonatelor fiind modificate prin valorile specificate. Metodele ce trebuie apelate pe un obiect de tip ''Shear'' pentru a realiza o scalare sunt: ''setX()'' și ''setY()'' ce indică factorii de multiplicare cu care sunt deplasate coordonatele în sensul pozitiv al axelor. == Combinarea Transformărilor == Mai multe efecte pot fi combinate prin specificarea unui lanț de transformări ordonat. Asocierea unei transformări se face prin intermediul metodelor ''add()'' / ''addAll()'' apelabilă pe lista observabilă de transformări asociată nodului curent, obținută ca valoare returnată de metoda ''getTransforms()''. === Animații === JavaFX oferă programatorului posibilitatea de a crea animaţii, sub forma tranziţiilor sau a animaţiilor bazate pe timp, implementate în clasele ''Transition'' şi ''Timeline'' din pachetul ''javafx.animation.Animation''. Tranziţiile implementează animaţiile asociindu-le o anumită cronologie (intern), acestea putând fi executate secvenţial sau în paralel. În JavaFX, au fost implementate mai multe tipuri de tranziţii: * ''FadeTransition'' – este folosită pentru a schimba opacitatea unui nod de-a lungul unei secvenţe de timp; constructorul primeşte durata (exprimată în milisecunde) şi nodul pe care se va realiza animaţia; ulterior se pot specifica valorile extreme ale transparenţei (prin metodele ''setFromValue()'' respectiv ''setToValue()''), dacă va fi reluată după terminarea ei (metoda ''setAutoReverse()'') şi numărul de rulări (metoda ''setCycleCount()''); rularea animaţiei se face prin metoda ''play()''; * ''PathTransition'' – este folosită pentru a muta un nod de-a lungul unei căi într-o anumită perioadă de timp; pentru o astfel de animaţie se specifică, suplimentar, calea (prin metoda ''setPath()'' care primeşte ca parametru un obiect de tip ''Path'' - care se construieşte din obiecte de tip ''CubicCurveTo'' care defineşte o curbă Bézier între elementul curent şi un anumit punct (x, y) trecând prin alte două puncte de control (''controlX1'', ''controlY1'') şi (''controlX2'', ''controlY2'') date ca parametrii la construirea obiectului) precum şi orientarea nodului respectiv de-a lungul căii (valorile posibile sunt exprimate în ''PathTransition.OrientationType''); * ''FillTransition'' – schimbă culoarea suprafeţei de umplere a unui nod de-a lungul unui interval de timp; valorile limită trebuie să aibă semnificaţia unor culori; * ''StrokeTransition'' – schimbă culoarea conturului unui nod de-a lungul unui interval de timp; valorile limită trebuie să aibă semnificaţia unei culori; * ''PauseTransition'' – folosită ca perioadă de aşteptare între tranziţii, în cadrul unei tranziţii secvenţiale sau apelată în cadrul ''Animation.onFinished()''; * ''RotateTransition'' – utilizată pentru rotirea unui nod cu un anumit unghi (dat în grade) de-a lungul unei perioade de timp; * ''ScaleTransition'' – folosită pentru scalarea unui nod cu anumite valori pe axele ''Ox'', ''Oy'', ''Oz''; pot fi folosiţi atât factori de multiplicare cât şi intervale de valori (pentru fiecare axă în parte); * ''TranslateTransition'' – modifică poziţia nodului între anumite coordonate (''x1'', ''y1'', ''z1'') → (''x2'', ''y2'', ''z2'') sau de la poziţia curentă la o poziţie obţinută prin intermediul unor multiplicatori; * ''ParallelTransition'' – utilizată spre a executa mai multe tranziţii simultan; spre exemplu, dacă se definesc mai multe tranziţii asupra unui nod, acestea pot fi adăugate unui obiect ''ParallelTransition'' prin metoda ''addAll()'' aplicabilă listei componentelor acestuia (obţinută cu metoda ''getChildren()'') pentru a fi executate în paralel; durata tranziţiei obţinute va fi dată de durata celei mai lungi tranziţii din cadrul listei asociate; * ''SequentialTransition'' – utilizată pentru a executa mai multe tranziţiile secvenţial, în ordinea în care au fost adăugate; durata tranziţiei este dată de suma duratelor tranziţiilor componente. În cadrul animaţiilor bazate pe timp, se consideră că nodul ce va fi animat este definit de anumite proprietăţi care pot fi modificate automat de către sistem atâta vreme cât se precizează valorile sale extreme (de început şi de sfârşit, numite cadre cheie – //eng.// key frames). De asemenea, astfel de obiecte (implementate de clasa ''Timeline'') au posibilitatea de a fi oprite (cu sau fără posibilitatea reluării animaţiei din punctul la care au fost oprite), de a fi redate în sens invers sau de a fi repetate. Un obiect ''KeyFrame'' primeşte la creare ca parametru durata animaţiei, metoda de tratare a evenimentului de tip terminare a anumației şi unul sau mai multe obiecte de tip ''KeyValue'' (care specifică un atribut ce se doreşte modificat în intervalul respectiv și valoarea cu care este modificat). JavaFX oferă posibilitatea de a folosi obiecte de interpolare (fie existente, fie definite de utilizator), specificând diferite poziţii intermediare ale obiectului pentru care se realizează animaţia. Un obiect ''Interpolator'' este specificat pentru un obiect ''KeyValue'' atunci când este creat. În cazul în care nu se specifică nici un obiect de tip ''Interpolator'', va fi folosit în mod implicit valoarea ''Interpolator.LINEAR''. Tipurile de interpolare predefinite sunt ''Interpolator.LINEAR'', ''Interpolator.DISCRETE'', ''Interpolator.EASE_BOTH'', ''Interpolator.EASE_IN'', ''Interpolator.EASE_OUT''. Dacă utilizatorul doreşte să construiască un obiect de tip interpolare, trebuie să definească o clasă (ce extinde ''Interpolator'') în care trebuie să definească metoda ''curve()'', folosită pentru a determina fracţia în implementarea metodei ''interpolate()''. === Redarea Conținutului Audio/Video === Pachetul ''javafx.scene.media'' oferă funcţionalităţi pentru redarea de conţinut multimedia în cadrul aplicaţiei JavaFX. Formatele suportate în mod curent sunt: * **audio**: MP3; AIFF şi WAV conţinând PCM (Pulse Code Modulation) necomprimat; MPEG-4 folosind AAC (Advanced Audio Coding); * **video**: FLV în care formatul video este VP6, iar cel audio este MP3; MPEG-4 cu compresie video H.264/AVC (Advanced Video Coding). Decodificarea unor tipuri de compresii audio sau video se bazează pe motoarele multimedia ale sistemelor de operare. Decodificarea unor formate AAC sau H.264/AVC au unele limitări dependente de platformă. JavaFX nu îşi propune să trateze toate formatele multimedia şi tipurile de codificare din aceste containere, ci să ofere un comportament consistent a acestor obiecte pe toate platformele pe care aceasta a fost implementată. Printre funcţionalităţile pe care le pune la dispoziţie pachetul JavaFX pentru conţinut multimedia se numără redarea tipurilor de formate amintite, suport pentru protocoalele HTTP şi FILE, descărcarea de conţinut în mod progresiv, căutare, folosirea unei zone de memorii tampon (//eng.// buffer) şi funcţii legate de redare (play, pause, stop, volume, mute, balance, equalizer). Totodată, poate fi redat conţinut aflat în Internet, parametrii de redare (rata de biţi pentru audio, rezoluţia pentru video) fiind ajustaţi în funcţie de condiţiile existente. Capabilităţile multimedia sunt extinse la comutarea între fluxuri alternative, aşa cum sunt specificate în cadrul listei de redare (//eng.// playlist) în funcţie de limitările impuse de reţea. Pentru un flux, se definește o listă de redare precum şi o listă de segmente în care fluxul este corupt. Fluxul poate avea formatul MP3 sau MPEG-TS ce conţine o multiplexare între conţinut audio AAC şi conţinut video H.264. Conceptul multimedia al JavaFX se bazează pe următoarele clase, care trebuie utilizate împreună pentru a implementa funcţionalitatea de redare a conţinuturilor de acest tip: * ''Media'' – o resursă multimedia, conţinând informaţii (metadate) despre aceasta; * ''MediaPlayer'' – componenta care oferă funcţionalităţile pentru redarea conţinutului multimedia respectiv; * ''MediaView'' – un obiect de tip ''Node'' care suportă animaţii şi efecte. **Clasa ''MediaPlayer''** oferă toate atributele şi metodele necesare pentru controlul redării de conţinut multimedia. Astfel, se poate preciza proprietatea ''AUTO_PLAY'', se poate apela direct metoda ''play()'' sau se poate specifica explicit numărul de repetări ale conţinutului respectiv. Atributele ''VOLUME'' şi ''BALANCE'' pot fi folosite pentru ajustarea nivelului de redare şi a echilibrului stânga-dreapta. Pentru atributul ''VOLUME'' se pot specifica valori cuprinse între 0.0 şi 1.0, iar pentru ''BALANCE'' valori cuprinse între -1.0 (doar stânga), 0 (centru) şi 1.0 (doar dreapta). Alte metode ce pot fi apelate pentru redarea conţinutului sunt ''pause()'' şi ''stop()''. Utilizatorul îşi poate defini mai multe metode de tratare pentru evenimente legate de starea obiectului care redă conţinutul şi anume: fluxul de date este transferat în zona tampon de memorie, redarea conţinutului s-a terminat, stagnare datorată faptului că datele nu au fost primite suficient de rapid spre a asigura redare continuă, producerea de erori definite în clasa ''MediaErrorEvent''. **Clasa ''MediaView''** extinde clasa ''Node'' şi oferă o vizualizare a conţinutului ce este redat, fiind responsabilă mai mult pentru efecte şi transformări. Atributul ''mediaPlayer'' specifică obiectul ''MediaPlayer'' care se ocupă cu redarea conținutului. Alte atribute sunt ''onError'', indicând metoda de tratare care va fi invocată când se produce vreo eroare, ''preserveRatio'' care specifică faptul că raportul lăţime/înălţime trebuie păstrat în cazul operaţiilor de scalare, ''smooth'' pentru ca operaţia de scalare să folosească un algoritm de filtrare mai performant (aceasta are loc în momentul în care conţinutul multimedia trebuie ajustat la dimensiunile obiectului care îl conţine, dimensiuni specificate de atributele ''fitHeight'' şi ''fitWidth''), ''viewport'' pentru cadrul de vizualizare al conţinutului multimedia, respectiv coordonatele ''x'' şi ''y'' ale originii obiectului. Scene scene = new Scene(new Group(), 500, 500); String source = getParameters().getRaw().get(0); Media media = new Media(source); // Media media = new Media(MEDIA_URL); MediaPlayer mediaPlayer = new MediaPlayer(media); mediaPlayer.setAudio(true); MediaView mediaView = new MediaView(mediaPlayer); ((Group)scene.getRoot()).getChildren().add(mediaView); Unui obiect de tip ''MediaView'' trebuie să i se adauge controale grafice din categoria controalelor JavaFX astfel încât utilizatorul să aibă funcţionalitatea pe care o oferă orice produs ce redă conţinut multimedia (cronometru şi bară de progres care indică momentul la care s-a ajuns, un buton pentru redare conţinut / oprire cu posibilitatea reluării sau nu precum şi un control pentru reglarea volumului). În implementarea acestor cerinţe funcţionale, pot fi utilizate metodele: * ''mediaPlayer.getStatus()'' care întoarce starea curentă a obiectului ''MediaPlayer'', valorile pe care le poate întoarce fiind ''Status.UNKNOWN'', ''Status.HALTED'', ''Status.PLAYING'', ''Status.PAUSED'', ''Status.READY'' sau ''Status.STOPPED''; * ''mediaPlayer.seek()'' care stabileşte ca redarea conţinutului să fie făcută începând de la momentul indicat ca parametru; * ''mediaPlayer.getStartTime()'' care identifică momentul de început al conţinutului multimedia; * ''mediaPlayer.setCycleCount()'' care precizează numărul de redări. Metodele de oportunitate pentru gestiunea evenimentelor ce pot apărea în procesul de redare al conţinutului sunt ''setOnPlaying()'', ''setOnPaused()'', ''setOnReady()'', ''setOnEndOfMedia()'', toate primind ca argument un obiect de tip ''Runnable'', reprezentând fire de execuţie în care sunt realizate astfel de operaţii. ===== Dezvoltarea unei Interfețe Grafice în mod vizual ===== ==== FXML ==== FXML reprezintă un limbaj bazat pe XML care oferă structura necesară pentru dezvoltarea unei interfeţe cu utilizatorul, separând-o de logica aplicaţiei. Astfel, se poate modifica aspectul grafic al unei aplicații fără a fi necesară compilarea codului sursă, putându-se obține interfețe cu utilizatorul complexe fără a avea cunoștințe referitoare la limbajul de programare Java. Deşi nu respectă în mod necesar o anumită schemă, FXML are o structură de bază predefinită. Astfel, cele mai multe clase JavaFX pot fi utilizate ca elemente şi cele mai multe proprietăţi (ale unor componente de tip JavaBeans) pot fi folosite ca atribute. Dintr-o perspectivă a arhitecturii MVC (Model View Controller), fişierul FXML care conţine descrierea interfeţei cu utilizatorul reprezintă partea de vizualizare. Controlul este realizat prin clasele Java (implementând opțional interfața ''Initializable'', responsabilă cu gestiunea unui fișier ''.fxml''), iar modelul va fi reprezentat prin obiecte domeniu (definite în context Java) ce se asociază vizualizării prin control. FXML este extrem de util în special pentru interfeţele cu utilizatorul ce conţin grafuri de scene complexe, de dimensiuni mari, formulare, animaţii complicate, precum şi pentru interfeţe statice (formulare, controale, tabele). Pot fi definite şi interfeţe dinamice prin utilizarea de scripturi. Utilizarea FXML implică numeroase beneficii: * are o sintaxă similară cu XML, punând la dispoziţia diferiţilor dezvoltatori un mecanism familiar pentru a construi interfeţe grafice; * FXML nu este un limbaj compilat, astfel încât proiectul nu trebuie reconstruit atunci când se produc modificări ca acestea să fie vizibile; * graful de scene este mai transparent în FXML, facilitând atât construirea cât şi menţinerea interfeţei cu utilizatorul; * conţinutul unui fişier FXML poate fi localizat pe măsură ce este parcurs; acest lucru nu se întâmplă în cazul codului sursă dezvoltat în limbajul de programare Java unde conţinutul fiecărui element trebuie modificat în mod manual (se obţine o referinţă către el după care se apelează metoda de stabilire a proprietăţii corespunzătoare); * FXML este compatibil cu orice limbaj JVM (Java Virtual Machine) ca: Java, Scala, Clojure; * FXML nu este limitat doar la partea de vizualizare a arhitecturii MVC, ci se pot construi servicii, sarcini sau obiecte domeniu, existând şi o integrare cu limbaje pentru scripturi (ca JavaScript). În versiunea 2.2 a JavaFX au fost introduse câteva facilităţi: * utilizarea ''\'' pentru a prefixa caracterele speciale; * definirea unei variabile implicite pentru controller spre a documenta spațiul de nume, realizând astfel o legătură cu interfața grafică; * constructor pentru clasa ''FXMLLoader'', ca alternativă a metodei statice ''load()''; * instanțierea clasei controller poate fi particularizată, prin specificarea unei fabrici în obiectul ''FXMLLoader'', astfel încât construirea unei astfel de clase este delegată către acesta; * foi de stil mai ușor de utilizat; * posibilitatea de definire a metodelor de tratare a evenimentelor în cadrul clasei controller, fără parametri; * eticheta '''' înlesneşte procesul de căutare a constantelor, astfel încât constanta ''java.lang.Double.NEGATIVE_INFINITY'' definită în Java poate fi referită ca ''''; * accesul la sub-clasele care asigură controlul unor noduri definite ca elemente ale scenei din fișierul FXML a fost îmbunătăţit – pentru fiecare nod se pot defini clase în cadrul cărora sunt tratate evenimente; în cazul în care acesta conţine la rândul său alte noduri, atunci când acestea sunt create se vor construi şi sub-clasele care asigură controlul (în cadrul metodei ''initialize()''); * iniţializarea claselor care asigură controlul se face prin mecanismul de reflectare, instanţa clasei ''FXMLLoader'' identificând automat metoda ''initialize()'' pe care o şi apelează; în cazul în care nu este publică, pentru această metodă trebuie folosită adnotarea ''@FXML''; * uşurinţa în realizarea de noi controale FXML – prin intermediul metodelor ''setRoot()'' şi ''setController()'', codul sursă care le apelează poate stabili valori pentru nodul rădăcină, respectiv pentru obiectul care se ocupă de control astfel încât aceste operaţii nu mai trebuie realizate de către ''FXMLLoader''; se pot dezvolta astfel controale care pot fi reutilizate. === Exemplu === O interfață grafică pentru procesul de autentificare în cadrul unei aplicații poate fi realizată prin intermediul unui fișier FXML: