Interfetele utilizator (IU) in Java, folosing Swing si NetBeans
IU asigura interactiunea dintre program si utilizator. Ele pun la dispozitia utilizatorului un set de componente vizuale menite sa afiseze date sau sa preia optiunile utilizatorului. Swing este framework Java cu ajutorul caruia pot fi programate IU. In continuare vom folosi terminologia Swing pentru a denumi diversele componente care contribuie la o IU. NetBeans pune la dispozitia programatorului un modul vizual de proiectare a IU. Pentru a-l folosi, cel mai simplu mod este de a crea un nou proiect (File->New Project), si sa folosim categoria de proiecte "Java", cu proiectul "Java Desktop Application". Cand vom crea acest proiect, rezultatul va arata ca un imaginea urmatoare:
Putem observa in NetBeans, pe langa ferestrele clasice (Projects, Output, Tasks) si urmatoarele noi ferestre:
designerul vizual - NetBeans trateaza special clasele care sunt derivate din ferestrele Swing, de exemplu FrameView (cum este clasa ExempluIUView.java in cazul de fata), oferind optiunea de a le vizualiza atat sub forma de cod sursa Java (optiunea "Source"), cat si sub forma vizuala (optiunea "Design")
inspectorul ierarhiei vizuale (Inspector) - prezinta sub o forma arborescenta toate componentele vizuale care alcatuiesc IU. De aici putem selecta usor, redenumi, muta, sterge, etc. Inspectorul poate fi folosit foarte eficient pentru a adauga noi componente in locul dorit, facand in el click dreapta pe containerul parinte si apoi "Add From Palette"
paleta de componente vizuale (Palette) - cuprinde toate componentele de care ne putem folosi pentru a structura o IU, cum ar fi containere, controale, meniuri, etc
proprietatile componentei vizuale selectate (Properties) - este o lista cu proprietati (nume, continut, culori, ...) pentru componenta selectata. De aici putem schimba diverse proprietati ale ei
evenimentele asociate componentei vizuale selectate (Events) - este o lista in care se pot seta metode care sa fie apelate cand apare un anumit eveniment legat de acea componenta vizuala
proprietati ale codului generat pentru componenta vizuala selectata (Code) - in aceasta lista se poate schimba de exemplu nivelul de acces pentru o componenta (public,private,protected), in asa fel incat ea sa fie vizibila si din alte clase
Pe langa ferestrele prezentate mai sus, se mai afla si altele (Binding, ...), pe care nu le vom detalia aici, ele fiind folosite in situatii speciale. In continuare vom prezenta cateva concepte pe care Swing le foloseste pentru a crea IU:
ferestre (Dialog, Frame, ...) - ferestre care apar pe ecran. Ele pot fi modale (utilizatorul trebuie sa o inchida pentru a reveni la fereastra din care a plecat), sau nemodale (utilizatorul poate simultan sa interactioneze atat cu ele cat si cu fereastra de baza)
containere de componente (Containers) - sunt componente care pot cuprinde unul sau mai multe componente, alineate in diverse feluri. De exemplu, daca avem o lista de persoane care poate sa fie mai mare decat portiunea de pe ecran, o putem introduce intr-un container JScrollPane, care in mod automat ne va asigura posibilitatea de a derula lista. Daca avem un dialog care prezinta simultan mai multe pagini de optiuni (tabs), putem folosi JTabbedPane, fiecare pagina din acesta primind un tab cu care poate fi accesata. Foarte mult se foloseste JPanel, care este un container simplu, in interiorul caruia putem alinia alte componente. Containerele pot fi incluse la randul lor in alte containere, pentru interfete mai complexe
aranjamente (layouts) - sunt asociate unui container si se selecteaza cu "click dreapta->Set Layout" pe acel container (in Inspector sau in Design). Aranjamentele permit ca elementele dintr-un container sa fie structurate in anumite feluri (vertical, orizontal, tabelar, etc), si redimensionate automat atunci cand fereastra este redimensionata. Pentru detalii privind diversele aranjamente se poate consulta pagina: http://download.oracle.com/javase/tutorial/uiswing/layout/layoutlist.html
controale (controls) - reprezinta componente individuale, care nu pot contine alte elemente (butoane, etichete, ...)
proprietati (properties) - proprietatile componentelor pot fi accesate cu metode de tip "get" si "set". De exemplu, proprietatea "text", care este textul afisat intr-o eticheta, buton, camp de text, ..., poate fi setata cu metoda "setText" a componentei respective si poate fi accesata cu metoda "getText"
evenimente (events) - reprezinta actiuni care au loc atunci cand apar anumite evenimente pe o componenta, de exemplu cand utilizatorul apasa un buton, scrie un text, selecteaza un item dintr-o lista, etc. Pentru a seta o metoda care sa trateze un eveniment specific, se face click dreapta pe componenta dorita si de le "Events" se selecteaza pentru ce eveniment dorim sa implementam actiunea. Inainte de a implementa evenimente pentru diverse componente, este bine sa dam acestor componente nume cat mai sugestive, pentru ca ulterior sa ne dam seama la ce se refera acea metoda. De exemplu, daca avem un buton care cauta un nume intr-o baza de date, putem sa-l denumim (cu click dreapta pe el in "Design" sau in "Inspector" si "Change Variable Name") ca fiind "cautaNumeBtn" (e bine sa adaugam la sfarsit un mic sufix care sa ne aminteasca ce componenta este aceea). Un aspect important legat de aplicatiile care implementeaza IU este faptul ca modelul de programare folosit este bazat pe tratarea evenimentelor (event driven). In acest model de programare, aplicatia asteapta diverse evenimente care pot sa apara (apasari de mouse sau pe tastatura, redimensionari de ferestre, etc), trateaza evenimentele aparute si apoi ramane din nou in asteptare
In continuare vom realiza o mica aplicatie, care genereaza o serie de numere aleatoare distincte (initial numerele sunt in ordine) intr-o lista din stanga ferestrei, le inmulteste cu o valoare data, iar apoi afiseaza rezultatele intr-o lista din dreapta. Numarul de valori generate se va putea modifica dintr-un dialog de setari. In final, aplicatia va arata astfel (sub ferestre este figurat cum trebuie sa arate inspectorul pentru fiecare fereastra):
Aplicatia de baza
Dialogul de setari
Vom completa aplicatia in urmatorii pasi:
Stergem bara de stare (statusPanel), selectand-o din "Inspector" si apasand "Delete". In acelasi timp din cod (pe care il accesam cu "Source", va trebui sa stergem tot ce se afla in "public ExempluIUView(SingleFrameApplication app)" dupa "initComponents();" si totodata toate variabilele dintre (inclusiv) "messageTimer" si "busyIconIndex". Adaugam urmatoarele variabile in clasa:
public int nValori=10; //numarul de valori afisare
public int multiplicator=5; //valoarea cu care se multiplica
public Integer[] valori=new Integer[nValori]; //array cu valorile curente de multiplicat
In "public ExempluIUView(SingleFrameApplication app), dupa "initComponents();" vom insera linia:
for(int i=0;i<nValori;++i)valori[i]=i;
prin care vom intializa primele valori. Totodata, vom adauga urmatoarele 2 metode:
void generare()
{
valori=new Integer[nValori];
Random rnd=new Random();
for(int i=0;i<nValori;++i){
int v;
boolean gasit;
do{
gasit=false;
v=Math.abs(rnd.nextInt())%(nValori*2);
for(int j=0;j<i;++j){
if(valori[j]==v){
gasit=true;
break;
}
}
}while(gasit);
valori[i]=v;
}
srcL.setListData(valori);
}
void calcul()
{
String[] a=new String[nValori];
for(int i=0;i<nValori;++i){
a[i]=""+valori[i]+"*"+multiplicator+"="+(valori[i]*multiplicator);
}
dstL.setListData(a);
}
Metoda generare() este apelata cand dorim sa generam un nou set de valori si genereaza un sir de valori pozitive aleatorii distincte, normate in asa fel incat sa fie suficient de mici. Metoda calcul() afiseaza in lista de destinatie rezultatele inmultirii valorilor curente cu multiplicatorul. Un aspect important este urmatorul: listele Swing (si alte componente), pot primi ca array-uri nu doar texte ci si obiecte generice (dar nu valori - din acest motiv s-a folosit un array de Integer si nu de int). Din aceste obiecte, valoarea de afisat este construita folosind functia toString(). Astfel, putem sa includem direct in lista o baza de date de persoane, inserand direct obiecte "Persoana" iar apoi, cand este selectat un anumit obiect si vrem sa-i accesam si alte proprietati, putem sa obtinem foarte simplu o referinta catre obiectul selectat ( de exemplu cu getSelectedValue() ), fara sa trebuiasca sa obtinem prima oara un text, iar apoi sa cautam textul in baza de date cu persoane.
Pentru construirea ferestrei de baza, se va tine cont de structura care este descrisa in inspectorul vizual prezentat mai sus. Pentru a se evita latirea butoanelor si a casetelor de text, se vor folosi proprietatile acestora: minimumSize, preferredSize, maximumSize. In bara de jos doar eticheta "( valori:10 )" este extensibila (maximumSize la o valoare foarte mare), cu textul centrat pe mijloc, pentru a ocupa spatiul. Valorile initiale se vor da din proprietatile componentelor respective. Se observa ca au fost schimbate (in Inspector), doar numele componentelor cu care se interactioneaza in program, pentru a fi mai descriptive. Celelalte componente au fost lasate cu numele lor implicite.
Dialogul de setari se creaza cu click dreapta in "Projects", pe package-ul "exempluiu", "New->JDialog Form" si ii dam numele "SetariDlg". Alinierea centrala a celor doua butoane a fost obtinuta prin includerea lor intrun panel de dimensiune mai mica decat latimea ferestrei. Distanta dintre cele doua butoane s-a creat prin inserarea intre ele a unui component de tip Filler, cu rol de a ocupa spatiul suplimentar dintre butoane, atunci cand acestea au o dimensiune maxima predefinita.Spatierea pe verticala s-a obtinut prin acordarea corespunzatoare a dimensiunilor maxime pe verticala a celor doua panele "top-level" din dialog. Atunci cand layout-ul calculeaza inaltimile lor actuale, tine cont si de dimensiunile lor maxime ca valori relative, unul fata de celalalt, si in functie de aceasta proportie le redimensioneaza pe fiecare corespunzator. "nValoriTF" trebuie sa fie facuta publica, folosind fereastra "Code", pentru a putea fi setata din exterior.
In clasa SetariDlg vom insera variabila "public int nValori;". Ca o conventie, daca s-a apasat pe "Aplica", aceasta variabila va contine noua setare pentru numarul de valori. Daca s-a apasat pe "Renunta", ea va contine "Integer.MIN_VALUE". Codul pentru evenimentul "actionPerformed" al celor doua butoane este:
private void renuntaBtnActionPerformed(java.awt.event.ActionEvent evt) {
nValori=Integer.MIN_VALUE;
setVisible(false);
}
private void aplicaBtnActionPerformed(java.awt.event.ActionEvent evt) {
try{
nValori=Integer.parseInt(nValoriTF.getText());
}catch(NumberFormatException e){nValori=1;}
if(nValori<1)nValori=1;
setVisible(false);
}
Se constata ca inchiderea dialogului se face cu "setVisible(false);". Desi dialogul dispare de pe ecran, el ramane totusi in memorie, astfel ca valorile membrilor sai pot fi accesate. Deoarece metoda "Integer.parseInt" genereaza o exceptie atunci cand se incearca sa se converteasca un sir de caractere care nu reprezinta un numar, ea a fost inclusa in try..catch, in aceasta situatie folosindu-se o valoare implicita. In general, atunci cand se interactioneaza cu utilizatorul, trebuie acordata o mai mare atentie validarii datelor de intrare, pentru a se evita valorile gresite. In cazul acestora se poate pune ori o valoare implicita, ori atentiona utilizatorul ca valoarea este incorecta si trebuie sa introduca o alta valoare.
In cazul editorului vizual din NetBeans, asocierea dintre o metoda care trateaza un anumit eveniment (de exemplu "aplicaBtnActionPerformed") si evenimentul "actionPerformed" al butonului "aplicaBtn", se face prin click dreapta pe buton->Events->Action->actionPerformed. In mod analog, trebuie asociate toate metodele de tratare a evenimentelor cu evenimentele care le apeleaza.
Pentru dialoguri simple, ca cel de mai sus, de multe ori nu este necesar sa se creeze o clasa speciala, ci se poate folosi direct un obiect de tipul "JOptionPane". Pentru mai multe exemple de folosire a "JOptionPane", se poate consulta: http://www.java2s.com/Tutorial/Java/0240__Swing/0960__JOptionPane-Dialog.htm . Pentru acest laborator s-a preferat varianta mai complexa, pentru a se ilustra modul de operare cu dialoguri.
Revenind la fereastra principala, va trebui sa tratam 3 evenimente: 2 "actionPerformed" pentru butoanele "Generare" si "Setari" si "keyReleased" pentru "multiplicatorTF". "keyReleased" se genereaza de fiecare data cand o tasta nu mai este apasata si avem nevoie de el pentru a detecta cand s-a modificat valoarea din "multiplicatorTF", astfel incat sa putem recalcula valorile rezultate. Daca la un moment dat nu mai avem nevoie de un eveniment, el se poate sterge din fereastra "Events". Codul celor 3 metode arata astfel:
private void setariBtnActionPerformed(java.awt.event.ActionEvent evt) {
SetariDlg dlg=new SetariDlg(null, true);
dlg.setLocationRelativeTo(mainPanel);
dlg.nValoriTF.setText(""+nValori);
dlg.setVisible(true);
if(dlg.nValori!=Integer.MIN_VALUE){
nValori=dlg.nValori;
nValoriL.setText("( valori: "+nValori+" )");
generare();
calcul();
}
}
private void generareBtnActionPerformed(java.awt.event.ActionEvent evt) {
generare();
calcul();
}
private void multiplicatorTFKeyReleased(java.awt.event.KeyEvent evt) {
try{
multiplicator=Integer.parseInt(multiplicatorTF.getText());
}catch(NumberFormatException e){multiplicator=0;}
calcul();
}
Butonul "Setari" creaza o noua instanta de "SetariDlg", o centreaza in mijlocul ferestrei de baza, ii seteaza "nValoriTF" cu valoarea curenta si apoi o face vizibila in mod modal. Din acest motiv, executia in fereastra principala se opreste la linia "dlg.setVisible(true);", pana cand dialogul va fi facut invizibil. Din acest moment se testeaza daca s-a facut dialogul invizibil cu butonul de "Aplica" sau de "Renunta" si doar in primul caz se preia noua valoare pentru numarul de valori, se afiseaza in eticheta si cu ea se regenereaza valorile si rezultatele. Cand se apasa pe butonul "Generare", se invoca metodele de "generare()" si "calcul()". Cand s-a apasat o tasta in "multiplicatorTF", se preia noua valoare, se converteste la intreg si se recalculeaza rezultatele.
Pentru informatii suplimentare privind Swing, se poate consulta documentatia de la: http://www.java2s.com/Tutorial/Java/0240__Swing/Catalog0240__Swing.htm sau http://download.oracle.com/javase/tutorial/uiswing/index.html