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 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 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 DatePicker și 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.

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.

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.

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.

BookStore.java
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().

Authentication.java
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.

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<ActionEvent> 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<Toggle> pentru care trebuie implementată metoda changed():

final ToggleGroup toggleGroup = new ToggleGroup();
toggleGroup.selectedToggleProperty().addListener(
    (ObservableValue<? extends Toggle> 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
SELECTED=true

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<Number>, 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<ActionEvent> 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<Object> listView = newListView<>();
ObservableList<Object> 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<Model, String> column = new TableColumn<Model, String>(attribute);
    column.setMinWidth(tableContentTableView.getPrefWidth() / attributes.size());
    column.setCellValueFactory(new PropertyValueFactory<Model, String>(attribute));
    tableContentTableView.getColumns().add(column);
}
populateTableView(null);
public void populateTableView(String whereClause) {
    try {
        ArrayList<ArrayList<String>> values = DataBaseWrapperImplementation.getInstance().getTableContent(tableName, 
            null, 
            (whereClause == null || whereClause.isEmpty()) ? null : whereClause, 
            null, 
            null);
        ObservableList<Model> data = FXCollections.observableArrayList();
        for (ArrayList<String> 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> (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:

  1. definirea nodurilor din cadrul arborelui (obiecte de tip TreeItem)
    final TreeItem<String> childNode1 = new TreeItem<>(new Person("FirstName1, LastName1"));
    final TreeItem<String> childNode2 = new TreeItem<>(new Person("FirstName2, LastName2"));
  2. definirea nodului rădăcină (obiect de tip TreeItem)
    final TreeItem<String> rootNode = new TreeItem<>("Persons");
    rootNode.setExpanded(true); 
  3. stabilirea relațiilor de tip părinte-copil între nodurile din cadrul arborelui
    rootNode.getChildren().setAll(childNode1, childNode2);
  4. definirea atributelor (coloanelor)
    TreeTableColumn<Person, String> firstNameColumn = new TreeTableColumn<>("First Name");
    TreeTableColumn<Person, String> lastNameColumn = new TreeTableColumn<>("Last Name");
  5. 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<Person, String> parameter) -> 
        new ReadOnlyStringWrapper(parameter.getValue().getValue().getFirstName()));
    lastNameColumn.setCellValueFactory((CellDataFeatures<Person, String> parameter) -> 
        new ReadOnlyStringWrapper(parameter.getValue().getValue().getLastName()));
  6. construirea unui obiect de tip TreeTableView
    final TreeTableView<String> treeTableView = new TreeTableView<>(rootNode);
    treeTableView.setShowRoot(true);
  7. 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.

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<ActionEvent> (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<TitledPane>.

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<? extends TitledPane> 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<Color>. 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<LocalDate>() {
 
    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<DatePicker, DateCell>() {
    @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<File> 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<ActionEvent> 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<String> tableNames = databaseWrapper.getTableNames();
        for (String tableName: tableNames) {
            MenuItem menuItem = new MenuItem(Utilities.tableNameToMenuEntry(tableName));
            menuItem.addEventHandler(EventType.ROOT, (EventHandler<Event>)this);
            databaseManagementMenu.getItems().add(menuItem);
        }
    } catch (SQLException sqlException) {
        System.out.println("An exception has occured: "+sqlException.getMessage());
  	if (Constants.DEBUG)
	     sqlException.printStackTrace();
    }
    // ...
}

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<String, Number> lineChart = new LineChar<String, Number>(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<PopupFeatures, WebEngine>() {
    @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<? extends State> 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:

<a href="about:blank" onclick="myObject.myMethod()">...</a>

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 (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_style.css
.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).

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.

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ă.

Î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<? super event-class> 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 <? super event-class> 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<MouseEvent>() { 
    @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);

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<MouseEvent>() {
                        @Override
                        public void handle(MouseEvent) { ... };
                    });
EventHandler filter = new EventHandler(<InputEvent>() {
    @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<DragEvent>() {
                         @Override
                         public void handle(DragEvent) { ... };
                    });
EventHandler handler = new EventHandler<InputEvent>() {
    @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 Translatepentru 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 <fx:constant> înlesneşte procesul de căutare a constantelor, astfel încât constanta java.lang.Double.NEGATIVE_INFINITY definită în Java poate fi referită ca <Double fx:constant=“NEGATIVE_INFINITY”/>;
  • 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:

authentication.fxml
<?xml version="1.0" encoding="UTF-8"?>
 
<?import java.lang.*?>
<?import java.net.*?>
<?import java.util.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.paint.*?>
 
<AnchorPane fx:id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="128.0" prefWidth="336.0000999999975" style="-fx-background-color: beige;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ro.pub.cs.aipi.lab09.controller.Authentication">
  <children>
    <GridPane layoutX="15.0" layoutY="8.0" prefWidth="309.0">
      <children>
        <Label text="User name:" GridPane.columnIndex="0" GridPane.rowIndex="0" />
        <Label text="Password:" GridPane.columnIndex="0" GridPane.rowIndex="1" />
        <TextField fx:id="usernameTextField" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="0" />
        <PasswordField fx:id="passwordTextField" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
        <HBox prefHeight="100.0" prefWidth="200.0" spacing="5.0" GridPane.columnIndex="1" GridPane.columnSpan="2147483647" GridPane.rowIndex="2">
          <children>
            <Button mnemonicParsing="false" onAction="#okButtonHandler" text="OK" />
            <Button mnemonicParsing="false" onAction="#cancelButtonHandler" text="Cancel" />
          </children>
          <padding>
            <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
          </padding>
        </HBox>
        <HBox id="rezultat" prefHeight="100.0" prefWidth="200.0" GridPane.columnIndex="0" GridPane.columnSpan="2147483647" GridPane.rowIndex="3">
          <children>
            <Label fx:id="errorLabel" textFill="RED" />
          </children>
        </HBox>
      </children>
      <columnConstraints>
        <ColumnConstraints hgrow="SOMETIMES" maxWidth="155.0" minWidth="10.0" prefWidth="114.0" />
        <ColumnConstraints hgrow="SOMETIMES" maxWidth="206.0" minWidth="10.0" prefWidth="195.0" />
      </columnConstraints>
      <rowConstraints>
        <RowConstraints maxHeight="30.0" minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
        <RowConstraints maxHeight="30.0" minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
        <RowConstraints maxHeight="30.0" minHeight="10.0" prefHeight="30.0" valignment="CENTER" vgrow="SOMETIMES" />
        <RowConstraints maxHeight="30.0" minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
      </rowConstraints>
    </GridPane>
  </children>
</AnchorPane>

Pe baza acestei ierarhii de nodurilor din fișierul FXML, se poate construi graful de scene:

De remarcat faptul că elementul rădăcină (de tip AnchorPane) specifică proprietatea fx:controller, indicând clasa care se va ocupa de gestiunea fișierului FXML, aceasta fiind ro.pub.cs.aipi.lab09.controller.Authentication.

Controalele care vor fi referite în codul sursă au specificat un identificator (proprietatea fx:id). Acestea vor fi adnotate cu @FXML, nefiind necesar ca ele să mai fie instanțiate, putând fi utilizate odată ce scena corespunzătoare fișierului FXML a fost încărcată.

@FXML private TextField     	usernameTextField;
@FXML private PasswordField 	passwordTextField;
@FXML private Label             errorLabel;  

Unele metode de tratare a evenimentelor sunt precizate explicit (#okButtonHandler, #cancelButtonHandler), clasa controller trebuind să le implementeze în mod obligatoriu, în caz contrar generându-se o excepție la încărcarea fișierului FXML. Și acestea trebuie adnotate cu @FXML:

@FXML 
protected void okButtonHandler(ActionEvent event) {
    // ...
}  
 
@FXML 
protected void cancelButtonHandler(ActionEvent event) {
    // ...
} 

Încărcarea documentului FXML se realizează în cadrul unei scene (obiect de tipul javafx.scene.Scene) prin intermediul metodei statice load() din clasa FXMLLoader:

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();
}

JavaFX Scene Builder

Documentele FXML pot fi dezvoltate în mod vizual folosind utilitarul SceneBuilder, disponibil pe platformele Windows (32/64 de biţi), Mac OS X şi Linux (32/64 biţi) – sub formă de pachete .deb şi arhive tar.gz.

Acesta permite construirea interfeței grafice interactiv, simultan cu generarea automată a documentului FXML, fiecare control putând fi configurat, grupate în proprietăţi, moduri de dispunere şi cod.

Caracteristicile utilitarului SceneBuilder sunt:

  • interfaţă WYSIWYG de tip drag and drop care permite crearea rapidă a unei interfeţe cu utilizatorul fără a fi nevoie de a scrie cod sursă; controalele pot fi preluate dintr-o bibliotecă şi dispuse în modul în care se doreşte afişarea lor;
  • integrare facilă cu orice IDE pentru Java de vreme ce este un utilitar de sine stătător;
  • generare automată a codului FXML se produce la construirea interfeţei cu utilizatorul, acesta fiind menţinut separat de logica aplicaţiei şi foile de stil;
  • editare în timp real şi funcţionalităţi de previzualizare ce permit ca modificările la interfaţa cu utilizatorul să fie vizibile pe măsură ce sunt implementate, fără a fi necesar ca aplicaţia să fie compilată, reducând timpul de dezvoltare al proiectului;
  • acces la întreaga bibliotecă de controale JavaFX 8;
  • posibilitatea de specificare a unor elemente de interfață grafică definite de utilizator, importate din arhive JAR, fișiere FXML sau importate în panourile de ierarhie / conținut;
  • suport 3D, pentru obiectele de acest tip putând fi specificate proprietățile din panoul de inspectare (cu excepția proprietăților Material și Mesh); încă nu pot fi create obiecte 3D;
  • suport pentru text particularizat, prin implementarea containerului TextFlow ce poate conține mai multe noduri de tip Text, acestea putând fi gestionate autonom;
  • JavaFX Scene Builder Kit este un API ce permite integrarea funcționalităților direct în interfața grafică a unui mediu de dezvoltare integrat precum NetBeans, IntelliJ, Eclipse;
  • suport pentru CSS permite gestiunea flexibilă a modului în care arată aplicaţia;
  • suport pentru mai multe sisteme de operare, având pe toate un comportament consistent;
  • gratuitate pentru toate tipurile de utilizatori.

Utilitarul Scene Builder are interfața grafică împărțită în 7 secțiuni.

În secţiunea 1 (Library) este disponibilă o bibliotecă de componente cu care se poate construi interfaţa cu utilizatorul. Ele pot indica modul de dispunere al obiectelor sau pot constitui controale propriu-zise. Acestea sunt grupate pe categorii: Containers, Controls, Menu, Miscellaneous, Shapes, Charts, 3D.

În secţiunea 2 (Document) este prezentată:

  • ierarhia de noduri (Hierarchy). Un obiect care conţine şi alte obiecte la rândul său va avea asociată un semn de plus sau minus, după cum conținutul său poate fi expandat, respectiv restrâns;
  • elementele care pot fi injectate în codul sursă (Controller). Este specificată clasa controller asociată fișierului FXML precum și componentele care au precizat un identificator unic pe baza căruia pot fi accesate în codul sursă.

În secţiunea 3 se poate previzualiza interfaţa cu utilizatorul. Aici aceasta se construieşte propriu-zis scena prin operaţii de tip drag and drop. În partea de sus a secțiunii este indicată scena de grafuri pentru nodul curent. Dacă nodul care se adaugă interfeţei se află deasupra unui alt obiect, acesta va fi evidenţiat în mod deosebit, astfel încât să se poată determina cu exactitate ierarhia nodurilor din interfaţa cu utilizatorul. În situaţia în care un nod este adăugat peste un alt obiect, acesta va fi inclus în nodul respectiv, stabilindu-se o relaţie de tip părinte-fiu.

În secțiunea 4 (Inspector) pot fi căutate elemente fie din biblioteca de controale, fie din interfaţa grafică. O astfel de funcţionalitate este deosebit de utilă în special pentru interfeţele cu utilizatorul mai complexe, în care identificarea unui element este mai dificilă.

În secţiunea 5 (Properties) se pot stabili proprietăţile obiectului selectat în mod curent (efecte, transformări, asocierea unor foi de stil, aliniere, vizibilitate, opacitate, orientare).

În secţiunea 6 (Layout) sunt tratate aspecte legate de modul de dispunere a nodului (dimensiuni, aliniere, poziție, transformări, limite).

În secţiunea 7 (Code) sunt indicate metodele executate din clasa controller atunci când o acţiune de tipul respectiv se produce asupra interfeţei respective. Categoriile de evenimente incluse sunt main, drag and drop, evenimente legate de tastaura şi mouse, precum şi evenimente specifice dispozitivelor cu ecran tactil (rotire, glisare, atingere, modificarea rezoluţiei de vizualizare).

Modele de Date asociate Controalelor JavaFX

JavaFX utilizează un model JavaBeans pentru reprezentarea obiectelor, oferind suport pentru proprietăţi şi pentru crearea de dependenţe (eng. binding) între obiecte, exprimând relaţiile dintre acestea. Atunci când un obiect participă într-o relaţie de tip dependenţă, modificările asupra sa sunt imediat vizibile la nivelul obiectului cu care se află în legătură. Interfeţele de programare care oferă astfel de funcţionalităţi pot fi de nivel înalt, în care dependenţele se creează uşor şi de nivel scăzut, folosite mai ales în cazul în care se doreşte execuţie rapidă şi o utilizare redusă a memoriei. O astfel de funcţionalitate este extrem de utilă în dezvoltarea de interfeţe grafice cu utilizatorul întrucât informaţiile vizualizate sunt automat sincronizate cu sursa de date din care acestea sunt preluate.

O proprietate într-o anumită clasă care respectă acest model corespunde unui atribut al acesteia:

class MyClass {
 
    private SomeTypeProperty myAttribute = new SomeTypeProperty();
 
    // ...
 
}

În situaţia în care clasa oferă suport pentru proprietăţi, ea va implementa metodele setter şi getter: setMyAttribute() respectiv getMyAttribute() pentru stabilirea respectiv obţinerea valorii propriu-zise a atributului respectiv, dar şi metoda myAttributeProperty() care va întoarce exact atributul în cauză. Pentru această valoare poate fi indicat un obiect de tip ascultător, ce monitorizează modificările realizate asupra acestuia.

Dependenţele dintre obiecte pot fi realizate fie prin clase specifice tipului de date respectiv DataTypeBinding (unde DataType reprezintă tipul de date respectiv) fie direct prin clasa Bindings.

De asemenea, sunt definite o serie de interfeţe care permit obiectelor să fie notificate atunci când se modifică o valoare sau se produce invalidarea unor valori (InvalidationListener, ChangeListener primesc notificări produse de Observable şi ObservableValue). Obiectul ObservableValue încapsulează şi valoarea propriu-zisă şi transmite modificările ei către orice obiect ChangeListener înregistrat în timp ce Observable, care nu conţine şi valoarea în sine transmite înregistrările către InvalidationListener.

Implementarea proprietăţilor şi a dependenţelor în JavaFX suportă evaluarea leneşă (eng. lazy evaluation), ceea ce înseamnă că la momentul în care se produce o modificare, valoarea nu este recalculată imediat, ci numai dacă şi atunci când valoarea în cauză este solicitată. Astfel, dependenţa este invalidă dacă s-au produs modificări şi recalcularea nu s-a produs încă, fiind validată când acest proces este declanşat de solicitarea valorii.

Numai clasa InvalidationListener suportă atât evaluarea lentă cât şi evaluarea rapidă, în timp ce clasa ChangeListener impune evaluarea rapidă (chiar dacă ObservableValue suportă şi evaluarea leneşă) pentru că nu este posibil să se ştie dacă valoarea a fost cu adevărat modificată până ce nu este recalculată.
MyClass myObject = new MyClass();
myObject.myAttributeProperty().addListener(new ChangeListener() {
    @Override
    public void changed(Observable Value observableValue, Object oldValue, Object newValue) {
        // ...
    }
});
MyClass myObject = new MyClass();
myObject.myAttributeProperty().addListener(
    (Observable Value observableValue, Object oldValue, Object newValue) -> {
        // ...
    }
);

În JavaFX sunt definite mai multe colecţii de date în cadrul pachetului javafx.collections, acesta conținând:

  • interfeţe
    • ObservableList – o listă care permite ascultătorilor să identifice modificările la momentul la care se produc;
    • ListChangeListener – o interfaţă care primeşte notificările modificărilor realizate asupra unui obiect de tip ObservableList;
    • ObservableMap – o listă de asocieri cheie unică-valoare ce permite ascultătorilor să identifice modificările atunci când se produc;
    • MapChangeListener – o interfaţă care primeşte notificările modificărilor realizate asupra unui obiect de tip ObservableMap.
  • clase
    • FXCollections – o clasă de utilitare ce pune la dispoziţia programatorilor aceleaşi metode din clasa java.util.Collections;
    • ListChangeListener.Change – reprezintă o schimbare realizată asupra unui obiect ObservableList;
    • MapChangeListener.Change – reprezintă o schimbare realizată asupra unui obiect ObservableMap.

Interfeţele ObservableList şi ObservableMap sunt ambele derivate din javafx.beans.Observable şi java.util.List, respectiv java.util.Map, definind metode addListener() primind parametri ListChangeListener / MapChangeListener.

Crearea unor astfel de obiecte se face prin metodele statice ale clasei FXCollections: observableList() şi observableMap(), ce primesc ca argumente obiecte din clasele java.util.List, respectiv java.util.Map.

List<String> list = new ArrayList<String>();
ObservableList<String> observableList = FXCollections.observableList(list);
observableList.addListener(new ListChangeListener() {
    @Override
    public void onChanged(ListChangeListener.Change change) {
        while (change.next()) {
            if (change.wasAdded()) {
                // ...
            }
            if (change.wasRemoved()) {
                // ...
            }
            if (change.wasReplaced()) {
                // ...
            }
            if (change.wasPermutated()) {
                // ...
            }
        }
    }
});	
Map<String,String> map = new HashMap<String,String>();
ObservableMap<String,String> observableMap = FXCollections.observableMap(map);
observableMap.addListener(new MapChangeListener() {
    @Override
    public void onChanged(MapChangeListener.Change change) {
        // ...
    }
});

De remarcat faptul că obiectul de tip ListChangeListener.Change poate conţine informaţii despre mai multe modificări, motiv pentru care este necesar să se realizeze un interator pe acestea prin apelarea metodei next(), în timp ce MapChangeListener.Change conţine o singură schimbare, corespunzătoare operaţiei put() sau remove() care a fost realizată.

Gestiunea Concurenței în JavaFX

Pachetul javafx.concurrent pune la dispoziţie funcţionalităţi pentru dezvoltarea de aplicaţii ce rulează pe mai multe fire de execuţie, completând capabilităţile oferite de java.util.concurrent ţinând cont de firul de execuţie al aplicaţiei JavaFX şi de alte constrângeri specifice interfeţelor grafice. În acest caz, ideea este de a oferi o viteză de răspuns satisfăcătoare prin execuţia sarcinilor care consumă resurse către fire de execuţie care rulează în fundal.

Graful de scene JavaFX nu poate fi accesat decât din firul de execuţie responsabil de interfaţa cu utilizatorul (firul de execuţie al aplicaţiei JavaFX). Implementarea de sarcini care consumă resurse pe acest fir de execuţie face ca interfaţa cu utilizatorul să fie neresponsivă motiv pentru care acestea trebuie alocate unuia sau mai multor fire de execuţie care rulează în fundal, lăsând firul de execuţie al aplicaţiei JavaFX să se ocupe de evenimentele rezultate din interacţiunea cu utilizatorul.

Pachetul javafx.concurrent constă în interfaţa Worker şi două clase de bază Task şi Service, ambele implementând interfaţa Worker. Aceasta oferă funcţionalităţi necesare unui fir de execuţie care rulează în fundal pentru a comunica cu interfaţa grafică. Clasa Task, implementare observabilă a clasei java.util.concurrent.FutureTask, permite specificarea de sarcini asincrone în timp ce clasa Service le execută.

Clasa WorkerStateEvent generează evenimente de fiecare dată atunci când starea unui obiect Worker se modifică.

Clasele Task şi Service implementează interfaţa EventTarget astfel încât suportă ascultarea evenimentelor legate de starea acestor fire de execuţie.

Interfaţa Worker defineşte un obiect care realizează anumite sarcini pe unul sau mai multe fire de execuţie, starea sa fiind observabilă din contextul firului de execuţie al aplicaţiei JavaFX. Stările pe care le poate avea un obiect care implementează această interfaţă sunt:

  • READY (imediat după ce a fost creat);
  • SCHEDULED (atunci când este programat pentru o anumită sarcină)
  • RUNNING (după ce a fost pornit) - în cazul în care firul de execuţie este pornit în mod direct, el va trece mai înainte prin starea SCHEDULED înainte de RUNNING, chiar dacă nu a fost programat propriu-zis;
  • SUCCEEDED (dacă sarcina este executată cu succes, proprietatea value fiind transmisă rezultatului acestui obiect);
  • FAILED (dacă sarcina nu a putut fi finalizată, proprietatea exception fiind transmisă excepţiei care s-a produs)
  • CANCELED dacă sarcina este întreruptă de utilizator prin apelarea metodei cancel().

Progresul realizat pe parcursul execuţiei se poate obţine prin diferite proprietăţi precum totalWork, workDone şi progress.

Clasa Task este folosită pentru a implementa logica sarcinii care trebuie executată pe un fir de execuţie în fundal. Implementările clasei Task trebuie să suprascrie metoda call() pentru a realiza operaţiile dorite şi pentru a furniza rezultatul. Fiind invocată pe un fir de execuţie în fundal, aceasta nu poate accesa decât proprietăţi publice din cadrul firului de execuţie al aplicaţiei JavaFX, prin intermediul metodelor updateProgress, updateMessage şi updateTitle, în caz contrar generându-se excepţii. În situaţia în care sarcina este întreruptă, rezultatul metodei call() e ignorat. Derivată din java.util.concurrent.FutureTask care implementează interfaţa Runnable, clasa Task permite ca obiectele sale să fie transmise unui fir de execuţie ca parametru sau să fie folosite din interfaţa de programare Executor. De asemenea, obiectul de tip Task poate fi apelat prin metoda FutureTask.run() din contextul altui fir de execuţie ce rulează în fundal.

Thread thread = new Thread(task);
thread.setDaemon(true);
thread.start();
// ...
ExecutorService.submit(task);

Un obiect de tip Task trebuie să poată fi oprit de fiecare dată când se apelează metoda cancel() din contextul său, de aceea, în metoda call(), acesta trebuie să verifice periodic dacă nu cumva s-a produs un astfel de eveniment, folosind metoda isCanceled(). În cazul în care în metoda call() există apeluri blocante de tipul Thread.sleep(), şi metoda cancel() este apelată în timpul execuţiei acestei metode, se va genera o excepţie InterruptedException astfel încât tratarea excepţiei trebuie să verifice şi motivul care a generat-o.

Vizualizarea progresului unui obiect de tip Task în cadrul unui control aparţinând firului de execuţie al aplicaţiei JavaFX poate fi realizată prin operaţia de asociere (bind) între acestea, pe baza proprietăţii de progres, actualizată în cadrul metodei call() prin metoda updateProgress(). Aceasta actualizează mai multe proprietăţi, cum ar fi progress, totalWork sau workDone astfel încât poate fi apelată metoda progressProperty pentru a obţine progresul sarcinii.

Întrucât clasa Task defineşte un obiect care nu poate fi reutilizat, pentru implementarea unei astfel de funcţionalităţi trebuie folosită clasa Service. Aceasta este proiectată să execute un obiect de tip Task pe unul sau mai multe fire de execuţie care rulează în fundal. Obiectivul său este de a ajuta programatorul să implementeze interacţiunea corectă dintre firele de execuţie din fundal şi firul de execuţie al aplicaţiei JavaFX astfel încât metodele şi stările unei astfel de clase nu pot fi accesate decât din acest context. Un obiect Service poate fi pornit (prin metoda Service.start()), anulat sau repornit, observându-se, pe parcursul rulării sale starea proceselor din fundal. Atunci când se implementează subclase Service, parametrii de intrare trebuie expuşi către obiectul de tip Task ca proprietăţi ale acesteia. Un obiect de tip Service poate fi executat printr-un obiect Executor, printr-un fir de execuţie de tip daemon sau printr-un anumit tip de executor cum ar fi ThreadPoolExecutor.

De fiecare dată când starea unui obiect de tip Worker se schimbă, se produce un eveniment corespunzător, definit de clasa WorkerStateEvent: ANY, WORKER_STATE_CANCELLED, WORKER_STATE_FAILED, WORKER_STATE_READY, WORKER_STATE_RUNNING, WORKER_STATE_SCHEDULED, WORKER_STATE_SUCCEEDED. Acestea apelează metodele de tratare aferente şi apoi mecanismele de oportunitate cancelled(), failled(), ready(), running(), scheduled(), succeeded(), acestea fiind invocate pentru tranziţiile în stările respective. Metodele pot fi suprascrise de subclasele derivate din Task şi Service pentru a implementa logica aplicaţiei conform cerinţelor funcţionale.

Recomandări referitoare la Implementarea Aplicațiilor JavaFX

Recomandările referitoare la implementarea aplicaţiilor JavaFX vizează atât nivelul de logică a aplicaţiei, cât şi nivelul de prezentare.

În privinţa nivelului de logică a aplicaţiei, cele mai frecvent uzitate tehnici sunt:

  • folosirea ecranelor animate care să indice progresul acelor procese de lungă durată astfel încât aplicaţia să nu pară că nu răspunde la interacţiunea cu utilizatorul;
  • folosirea unor denumiri simbolice pentru pachetele implementate pentru ca aplicaţia să fie mai organizată şi mai uşor de întreţinut;
  • implementarea arhitecturii model-view-controller (MVC) prin intermediul fişierelor FXML pentru definirea scenelor, acestea reprezentând partea de vizualizare, în timp ce modelul este reprezentat de obiectele de domeniu specifice aplicaţiei, iar partea de control aparţine codului Java care defineşte comportamentul interfeţei grafice în interacţiunea cu utilizatorul;
  • rularea sarcinilor care implică procesarea unui volum mare de date pe fire de execuţie separate, care rulează în fundal, pentru a evita ca utilizatorul să aştepte foarte mult până la terminarea metodei.

Referitor la nivelul de prezentare, se recomandă următoarele practici:

  • utilizarea panourilor de dispunere, astfel încât conţinutul să fie organizat şi să poată fi utilizat cu uşurinţă;
  • asigurarea faptului că toate butoanele au aceeaşi dimensiune se poate realiza mai uşor prin încadrarea tuturor într-un panou de dispunere de tip VBox sau prin indicarea valorilor maxime pentru dimensiunile lor şi dispunerea într-un obiect de tip TilePane;
  • menţinerea nodurilor la dimensiunile preferate (Control.USE_PREF_SIZE) astfel încât în cazul unor operaţii de redimensionare să nu se producă comportamente nedorite;
  • prevenirea operaţiei de redimensionare prin specificarea dimensiunilor minime, maxime şi preferate la aceeaşi valoare;
  • alinierea nodurilor şi panourilor folosind metoda setAlignment() care primeşte ca parametru constante din pachetul javafx.geometry precum HPos pentru alinierea orizontală, VPos pentru alinierea verticală sau Pos pentru alinierea verticală şi orizontală; această constantă are forma Pos.VALIGN_HALIGN unde VALIGN poate fi TOP, BOTTOM, CENTER, iar HALIGN poate lua valorile LEFT, RIGHT, CENTER.
  • folosirea foilor de stil pentru definirea aspectului interfeţei grafice, aceasta putând fi modificată cu uşurinţă prin schimbarea foii de stil asociate.

Activitate de Laborator

Se doreşte implementarea unei interfeţe grafice cu utilizatorul pentru gestiunea informaţiilor dintr-o bază de date, aceasta urmând a fi utilizată pentru un sistem ERP destinat unei librării care comercializează doar cărţi.

Aplicaţia va avea două ferestre:

  • un formular pentru autentificarea utilizatorilor în sistem;
  • o fereastră în care sunt implementate operaţiile pentru o tabelă dintr-o bază de date (adăugare, modificare, ştergere, căutare).

0. Să se cloneze în directorul de pe discul local conținutul depozitului la distanță de la https://www.github.com/aipi2015/Laborator09. În urma acestei operații, directorul Laborator09 va trebui să conțină subdirectorul labtasks, fișierele README.md și LICENSE.

student@aipi2015:~$ git clone https://www.github.com/aipi2015/Laborator09.git

1. a) Să se ruleze, folosind MySQL Workbench (sau alt utilitar similar), scriptul Laborator09l.sql, localizat în directorul scripts din cadrul proiectului 09-BookStore-JavaFX. Acesta instalează baza de date bookstore.

În cazul în care baza de date bookstore este deja instalată, nu mai este necesar să se ruleze acest script.
Această operație poate dura o perioadă de timp îndelungată, în funcție de configurațiile mașinilor pe care se lucrează.

b) În interfața Constants din pachetul ro.pub.cs.aipi.lab09.general, să se modifice credențialele (nume de utilizator și parolă), astfel încât să se poată accesa informațiile din baza de date MySQL (proprietățile DATABASE_USERNAME respectiv DATABASE_PASSWORD).

2. Să se acceseze fişierul authentication.fxml (localizat în directorul src, subdirectorul resources/fxml) folosind utilitarul SceneBuilder sau cu orice alt editor de text – inclusiv din cadrul mediilor integrate de dezvoltare Eclipse / Eclipse. Acesta conține un formular pentru autentificarea utilizatorilor.

a) Să se precizeze clasa controller pentru documentul FXML (având ca rădăcină un mecanism de gestiune a conținutului de tip AnchorPane) ca fiind ro.pub.cs.aipi.lab09.controller.Authentication.

authentication.xml
<!-- different imports -->
 
<AnchorPane fx:id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" 
prefHeight="128.0" prefWidth="336.0000999999975" 
style="-fx-background-color: beige;" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="ro.pub.cs.aipi.lab09.controller.Authentication">
 
  <!-- description of the graphic user interface -->
 
</AnchorPane>

b) Pentru butoanele Ok / Cancel, să se specifice metodele care vor fi executate în cazul producerii unui eveniment de tipul ActionEvent.ACTION (proprietatea onAction()) ca fiind:

  • okButtonHandler();
  • cancelButtonHandler().

authentication.xml
<!-- ... -->
<Button mnemonicParsing="false" onAction="#okButtonHandler" text="OK" />
<!-- ... -->

c) Să se specifice identificatori (proprietatea fx:id) pentru:

  • cele două câmpuri text pentru specificarea parametrilor nume utilizator / parolă, având denumirile usernameTextField / passwordTextField;
  • eticheta în care se va afișa un mesaj de eroare în situația în care operația de autentificare eșuează, având denumirea errorLabel.

authentication.xml
<!-- ... -->
<TextField fx:id="usernameTextField" prefWidth="200.0" GridPane.columnIndex="1" GridPane.rowIndex="0" />
<!-- ... -->

d) În clasa controller ro.pub.cs.aipi.lab09.controller.Authentication, sa se implementeze metodele okButtonHandler() / cancelButtonHandler() ce gestionează accesul utilizatorului în aplicaţie. Vor avea dreptul de a accesa sistemul informatic doar utilizatorii al căror tip este administrator. În cazul în care combinaţia (utilizator, parolă) este incorectă sau utilizatorul nu are acces în sistem, se va afişa un mesaj corespunzător care va rămâne pe ecran maxim 5 secunde.

<spoiler|Indicații de rezolvare> În cazul butonului Ok, este necesar să se realizeze o interogare asupra tabelei user (accesibil prin proprietatea Constants.USER_TABLE_NAME). Aceasta va prelua atributul type (disponibil în câmpul Constants.USER_TYPE_ATRRIBUTE) pentru acele înregistrări care au câmpurile username și password egale cu valorile introduse prin intermediul interfeței grafice.

DatabaseOperations databaseOperations = DatabaseOperationsImplementation.getInstance();
List<String> attributes = new ArrayList<>();
attributes.add(Constants.USER_TYPE_ATTRIBUTE);
List<List<String>> userTypes = databaseOperations.getTableContent(
  Constants.USER_TABLE_NAME,
  attributes,
  "username=\'" + usernameTextField.getText() + "\' AND password=\'" + passwordTextField.getText() + "\'",
  null,
  null,
  null);

În urma execuției acestei interogări, se pot distinge următoarele situații:

  1. interogarea nu întoarce nici o valoare, ceea ce înseamnă că perechea (nume de utilizator, parolă) este incorectă: în această situație se va afișa un mesaj de eroare corespunzător, conținut de proprietatea Constants.ERROR_USERNAME_PASSWORD; acesta va fi afișat timp de 5 secunde, iar conținutul câmpurilor nume de utilizator și parolă vor fi golite;
  2. interogarea întoarce valori, situație în care se verifică tipul utilizatorului:
    1. utilizatorul are tipul administrator (reținut de Constants.USER_TYPE_ADMINISTRATOR_VALUE); în această situație se deschide interfața grafică reprezentată de clasa Container și se închide interfața grafică reprezentată de clasa (Authentication) - aceasta trebuie accesată prin intermediul grafului de scene atașat evenimentului, întrucât procesarea se face prin intermediul firului de execuție JavaFX, responsabil cu gestiunea operațiilor cu utilizatorul:
      new Container().start();
      ((Node) event.getSource()).getScene().getWindow().hide();
    2. utilizatorul are tipul client (reținut de Constants.USER_TYPE_CLIENT_VALUE): în această situație se va afișa un mesaj de eroare corespunzător, conținut de proprietatea Constants.ERROR_ACCESS_NOT_ALLOWED; acesta va fi afișat timp de 5 secunde, iar conținutul câmpurilor nume de utilizator și parolă vor fi golite.

Afișarea unui mesaj de eroare pentru care se atașează o animație de tipul dispariție graduală, pe parcursul unui interval de timp este realizată prin intermediul unui obiect de tip FadeTransition:

errorLabel.setText(...);
FadeTransition fadeTransition = new FadeTransition(Duration.seconds(5), errorLabel);
fadeTransition.setFromValue(1.0);
fadeTransition.setToValue(0.0);
fadeTransition.play();
usernameTextField.setText("");
passwordTextField.setText("");

În cazul butonului Cancel, se va apela metoda Platform.exit() </spoiler>

3. În clasa TableManagement, din pachetul ro.pub.cs.aipi.lab09.controller să se completeze metoda initialize(), astfel încât aceasta să populeze obiectul attributesGridPane (de tip GridPane) care va avea pe două coloane obiectele de tip ArrayList<Label> (attributeLabels), respectiv ArrayList<Control> (attributeControls).

Obictele attributeLabels, respectiv attributesControls au deja conținutul care trebuie vizualizat în cadrul acestora.

<spoiler|Indicații de Rezolvare> Înainte de a adăuga obiectul propriu-zis, trebuie să îi specificaţi poziţia în cadrul obiectului de tip GridPane, prin intermediul metodei GridPane.setConstraints(Node, column ,row).

Atașarea propriu-zisă a nodului se face prin intermediul metodei add() apelată pe lista observabilă a nodurilor (copil) a obiectului attributesGridPane, obținută prin invocarea metodei getChildren().

GridPane.setConstraints(attributesLabels.get(attributesLabels.size() - 1), 0, currentIndex);
attributesGridPane.getChildren().add(attributesLabels.get(attributesLabels.size() - 1));
 
// ...
 
GridPane.setConstraints(attributesControls.get(attributesControls.size() - 1), 1, currentIndex);
attributesGridPane.getChildren().add(attributesControls.get(attributesControls.size() - 1));

Toate aceste operații vor fi realizate repetitiv, pe o iterație care parcurge toate atributele din cadrul entității respective.

</spoiler>

Așa cum se poate observa, în interfaţa grafică se folosesc cinci butoane (adăugare, editare, ştergere, căutare, golire conținut) pentru a se realiza operaţiile de tip CRUD (Create, Read, Update, Delete).
În momentul când se produce o acţiune corespunzătoare unuia dintre aceste acestea, se vor apela metodele corespunzătoare evenimentului de tip onAction(), specificate în fișierul FXML.

4. În clasa TableManagement, din pachetul ro.pub.cs.aipi.lab09.controller, să se implementeze metoda updateButtonHandler() corespunzătoare butonului care actualizează valorile din controale (câmpuri text și liste de selecție) în tabelă.

<spoiler|Indicații de Rezolvare> În cadrul metodei updateButttonHandler() trebuie realizate următoarele operații:

  1. se obține indexul selectat în mod curent în cadrul tabelului (astfel încât selecția să poată fi refăcută ulterior operației de actualizare)
    int tableViewSelectedIndex = tableContentTableView.getSelectionModel().getSelectedIndex();
  2. se obține o listă cu toate valorile selectate în mod curent (astfel încât operația de actualizare să fie realizată pe baza identificatorului care are rol de cheie primară în tabelă)
    List<String> tableViewCurrentRecord = ((Model) tableContentTableView.getSelectionModel().getSelectedItem()).getValues();
  3. se parcurge lista de etichete / controale, astfel încât să se construiască:
    1. lista de atribute, pe baza listei de etichete
      attributes.add(attributesLabels.get(currentIndex).getText());
    2. lista de valori: se obține conținutul listei derulante și pe baza ei se determină identificatorul înregistrării din tabela referită, prin intermediul metodei getForeignKeyValue() care primește ca argumente denumirea tabelei, denumirea atributului precum și lista cu valori
      if (attributeControl instanceof ComboBox) {
        try {
          String parentTableValues = ((ComboBox<String>) attributeControl).getValue();
          if (parentTableValues != null && !parentTableValues.isEmpty()) {
            value = databaseOperations.getForeignKeyValue(tableName, attributesLabels.get(currentIndex).getText(), Utilities.decompress(parentTableValues));
          }
        } catch (Exception sqlException) {
          System.out.println("An exception had occured: " + sqlException.getMessage());
          if (Constants.DEBUG) {
            sqlException.printStackTrace();
          }
        }
      } else {
        value = ((TextField) attributeControl).getText();
      }
      if (value != null && !value.isEmpty()) {
        values.add(value);
      }
  4. actualizarea propriu-zisă este realizată prin intermediul metodei updateRecordsIntoTable() care primește ca parametrii denumirea tabelei, lista de atribute, lista de valori precum și condiția pentru înregistrarea care este actualizată (pe baza valorii cheii primare, determinată anterior)
    try {
      if (attributes != null && values != null && !attributes.isEmpty() && !values.isEmpty()) {
        databaseOperations.updateRecordsIntoTable(tableName, 
          attributes, 
          values,
          databaseOperations.getTablePrimaryKey(tableName) + "=\'" + tableViewCurrentRecord.get(0) + "\'");
      }
    } catch (SQLException | DatabaseException exception) {
      System.out.println("An exception had occured: " + exception.getMessage());
      if (Constants.DEBUG) {
        exception.printStackTrace();
      }
    }
  5. popularea tabelei cu noul conținut implică:
    1. apelarea metodei populate, fără nici un criteriu de selecție;
    2. selectarea valorii care a fost actualizată: tableContentTableView.getSelectionModel().select(tableViewSelectedIndex);;
  6. valorile corespunzătoare selecției curente nu mai sunt vizualizate în cadrul listei de controale; în acest scop se poate invoca metoda de tratare a evenimentului de apăsare a butonului Clear, fără nici un argument ca eveniment propriu-zis:
    clearButtonHandler(null);

</spoiler>

5. În clasa TableManagement, din pachetul ro.pub.cs.aipi.lab09.controller, să se implementeze metoda clearButtonHandler() care curăță conținutul câmpurilor text și al listelor de selecție, lăsând completată numai valoarea corespunzătoare cheii primare autoincrementale cu următoarea valoare din baza de date.

<spoiler|Indicații de Rezolvare> Se parcurge, în cadrul unei iterații, lista de controale, acestea putând avea tipul:

  1. ComboBox: se folosește metoda setValue() primind ca argument șirul vid: ((ComboBox<String>) attributeControl).setValue("");;
  2. TextField: se utilizează metoda setText() primind ca argument:
    1. valoarea maximă a cheii primare în cadrul tabelei (pentru indexul 0, corespunzător identificatorului), prin intermediul metodei getTablePrimaryKeyMaximimValue(), disponibilă în interfața DatabaseOperations: ((TextField) attributeControl).setText(Integer.toString(databaseOperations.getTablePrimaryKeyMaximumValue(tableName) + 1));;
    2. șirul vid, altfel: ((TextField) attributeControl).setText("");.

</spoiler>

6. Să se creeze un submeniu About al meniului Help care în momentul când este accesat deschide o fereastră în care sunt afişate pictograma (fișierul icon.png din directorul resources/images) și denumirea aplicaţiei, versiunea şi anul în care a fost realizată. Totodată, aceasta va conţine un buton care va închide fereastra atunci când este apăsat.
Se poate folosi clasa Dialog în acest sens.

<spoiler|Indicații de Rezolvare> În clasa Container din pachetul ro.pub.cs.aipi.lab09.controller se instanțiază un obiect de tip Dialog pentru care se specifică următoarele proprietăți:

  • titlul este Constants.ABOUT_WINDOW_TITLE;
  • locația de la care se va încărca pictograma este Constants.ABOUT_ICON_LOCATION;
  • mesajul care va fi vizualizat este Constants.ABOUT_MESSAGE_CONTENT.

Fereastra trebuie afișată prin invocarea metodei start().

Dialog dialog = new Dialog();
dialog.setProperties(Constants.ABOUT_WINDOW_TITLE, Constants.ABOUT_ICON_LOCATION, Constants.ABOUT_MESSAGE_CONTENT);
dialog.start();

</spoiler>

7. Să se creeze un buton de căutare la apăsarea căruia vor fi afişate în tabel toate înregistrările care corespund unor anumite criterii (au anumite valori corespunzătoare unor anumite atribute, specificate de utilizator în câmpurile text şi listele derulante, ignorând cheia primară).

<spoiler|Indicații de Rezolvare> În clasa TableManagement din pachetul ro.pub.cs.aipi.lab09.controller, se va implementa metoda searchButtonHandler(), în cadrul căreia va fi parcursă, în cadrul unei iterații, lista de controale, pentru a se obține perechea de atribute și de valori pe baza cărora va fi realizată căutarea.

  1. dacă controlul este de tip ComboBox se identifică valoarea din tabela referită corespunzătoare atributului, prin invocarea metodei getForeignKeyValue():
    if (attributeControl instanceof ComboBox) {
      try {
        String parentTableValues = ((ComboBox<String>) attributeControl).getValue();
        if (parentTableValues != null && !parentTableValues.isEmpty()) {
          value = databaseOperations.getForeignKeyValue(tableName, attributesLabels.get(currentIndex).getText(), Utilities.decompress(parentTableValues));
        }
      } catch (Exception exception) {
        System.out.println("An exception had occured: " + exception.getMessage());
        if (Constants.DEBUG) {
          exception.printStackTrace();
        }
      }
    }
  2. în situația în care controlul este de tip TextField, se identifică valoarea prin intermediul metodei getText(): value = ((TextField) attributeControl).getText();

Se construiește clauza WHERE a interogării pe baza căreia este realizată căutarea folosind șablonul atribut='%valoare%', aceasta fiind transmisă ca parametru al metodei populateTableView():

for (Control attributeControl : attributesControls) {
  // ...
  if (value != null && !value.isEmpty() && currentIndex != 0) {
    whereClause += ((whereClause.isEmpty()) ? "" : " AND ") + attributesLabels.get(currentIndex).getText() + " LIKE \'%" + Utilities.escape(value) + "%\'";
  }
  currentIndex++;
  // ...
}
populateTableView(!whereClause.isEmpty() ? whereClause : null);

</spoiler>

8. Să se creeze o animaţie asupra butonului deasupra căruia se găseşte mouse-ul astfel încât acesta să aibă un efect de tip umbră exterioară (DropShadow) implementând şi o tranziţie care pe parcursul unei secunde modifică transparenţa de la 1.0 la 0.8 şi scalează dimensiunile cu 0.1. Animaţia trebuie cât timp mouse-ul se găseşte deasupra butonului având un caracter ciclic. Metoda se va implementa pentru toate butoanele ce realizează modificări asupra înregistrărilor din baza de date.

<spoiler|Indicații de Rezolvare> Se creează următoarele efecte:

  1. o umbră care va fi aplicată butoanelor (un obiect de tip DropShadow);
  2. o animație de tip dispariție având transparențele variabile între 1 și 0.8, cu durata de 1 secundă, care se va repeta nedefinit (un obiect de tip FadeTransition);
  3. o animație de tip scalare, având valorile de 0.1 atât pe axa Ox cât și pe aza Oy, cu durata de 1 secundă, care va rula nedefinit (un obiect de tip ScaleTransition);
  4. un efect paralel, care include animațiile de tip dispariție, respectiv scalare, care vor fi aplicate concomitent (un obiect de tip ParallelTransition).
final DropShadow dropShadow = new DropShadow();
final FadeTransition fadeTransition = new FadeTransition(Duration.seconds(1));
fadeTransition.setFromValue(1.0f);
fadeTransition.setToValue(0.8f);
fadeTransition.setCycleCount(Timeline.INDEFINITE);
fadeTransition.setAutoReverse(true);
final ScaleTransition scaleTransition = new ScaleTransition(Duration.seconds(1));
scaleTransition.setByX(0.1f);
scaleTransition.setByY(0.1f);
scaleTransition.setCycleCount(Timeline.INDEFINITE);
scaleTransition.setAutoReverse(true);
final ParallelTransition parallelTransition = new ParallelTransition(fadeTransition, scaleTransition);

Pentru fiecare buton în parte, se vor defini metodele ascultător pentru fiecare dintre cele două evenimente:

  1. MouseEvent.MOUSE_ENTERED - se sta stabilește efectul de tip umbră, se transmite nodul ca obiect al animațiilor de tipul Fade și Scale, după care tranziția paralelă este începută , folosind metoda play();
  2. MouseEvent.MOUSE_EXITED - se pierd efectele anterioare (metoda setEfect() primește argumentul null), durata animației devine 0, animațiile pierd referința de pe nod, iar tranziția paralelă este oprită, folosind metoda stop().
buttonList.stream().map((currentButton) -> {
  currentButton.addEventHandler(MouseEvent.MOUSE_ENTERED, (MouseEvent event) -> {
    currentButton.setEffect(dropShadow);
    fadeTransition.setNode(currentButton);
    scaleTransition.setNode(currentButton);
    parallelTransition.play();
  });
  return currentButton;
}).forEach((currentButton) -> {
  currentButton.addEventHandler(MouseEvent.MOUSE_EXITED, (MouseEvent event) -> {
    currentButton.setEffect(null);
    parallelTransition.jumpTo(Duration.ZERO);
    fadeTransition.setNode(null);
    scaleTransition.setNode(null);
    parallelTransition.stop();
    parallelTransition.setNode(null);
  });
});

</spoiler>

Resurse

Soluții

laboratoare/laborator09.txt · Last modified: 2015/12/28 15:52 by Andrei Roșu-Cojocaru
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0