Graafikakomponendid, teated, kuularid Graafikakomponendid aitavad programmeerijal hõlbustada programmi ja kasutaja suhtlemist. Samad võimalused saab luua ka joonistamisvahendite abil, kuid varemloodud komponentide puhul peab kasutaja nende käsitsemist õppima vaid korra, kasutada mõistab aga igal pool kus nad ette tulevad. Samuti piisab programmeerijal vaid paigutada komponent sobivale kohale, muuta tema omadusi vastavalt programmi vajadustele ning paluda programmil reageerida kasutaja tegevusele vastavalt. Komponendi sisemise ehituse üle on vaja pead murda vaid siis, kui soovida tema võimalustele midagi lisada, ise uut komponenti luua või olemasoleva vigu parandada. Java keeles saab kasutada umbes kümmet operatsioonisüsteemi juurde kuuluvat ning ligi seitsekümmet java oma vahenditega loodud valmiskomponenti. Vajaduse korral aga saab neid täiendada ning ise juurde kombineerida ja luua. Operatsioonisüsteemi poolt pakutavad komponendid asuvad paketis java.awt, nad töötavad (vähemalt esialgu) kiiremini ehk nõuavad arvutilt vähem ressursse. Nad näevad välja vastavalt operatsioonisüsteemile ning nende järgi vaadates ei paista välja, et programm on java abil kirjutatud. Kui aga soovitakse (või on vajalik) et programmid erinevates keskkondades sarnaselt välja näeksid, siis tuleb kasutada "puhtaid" java komponente, mille värvus ning kuju operatsiooni-süsteemist ei sõltu. Sellised valmiskomponendid asuvad enamjaolt klassis javax.swing. Komponendid (nagu muudki objektid) saavad vastu võtta ning välja saata teateid. Saatja käivitab vastuvõtja meetodi. Komponendile teate saatmisel käivitatakse komponendi meetod (näiteks värvi muutmiseks). Teate saatmisel aga käivitab komponent (kui saatja ) vastuvõtja meetodi. Näiteks kui nupule vajutamisel muutub tekst tekstikastis suuremaks, siis öeldakse, et nupp saatis tekstikastile teate. Sisuliselt tähendab see, et nupule vajutamise tulemusena käivitatakse tekstikasti meetod, millega saab muuta srifti suurust. Et nupul oleks võimalik tekstikasti meetodit käivitada, peab nupule olema antud osuti tekstikastile ning öeldud, et juhul kui nupule vajutatakse, tuleb tekstivälja vastav meetod käima panna. Iseseisvalt saab ekraanil avada akent (Window) või raami (Frame). Java keeles tähistab aken lihtsalt ekraanipiirkonda mille kasutamist saab programm juhtida, raami puhul on sel piirkonnal ümber raam ning üleval nupud suurusemuutmiseks ja sulgemiseks ning pealkirjariba. Enamasti kasutatakse kasutaja mugavuse huvides programmides raami, kuid näiteks täisekraaniefektide loomiseks on akna kasutamine paratamatu. Nende suuruse määramisel saab arvestada ekraani suurust. Dialog on eriline raam, mille avamisel võib jätta programmi töö seniks pooleli, kuni kasutaja on vastuse sisestanud. Sulgemisnupule vajutamine ei sule raami automaatselt. Vajutuse juurde on võimalik siduda programmilõik, kus kirjeldatakse programmi vajutusjärgset käitumist. Seal saab näiteks küsida, et "kas soovite salvestada" või muud taolist. Tekstiväli (TextField) ning tekstiala (TextArea) võimaldavad ekraanile näidata ning kasutajal sisestada teksti. Esimesel neist võib olla vaid üks rida, tekstiala puhul aga saab määrata, mitut rida talle tahetakse. Värvi ning ?rifti on võimalik muuta vaid kogu teksti juures korraga, keerulisemat kujundust nõudvate tekstide puhul tuleks kasutada swingi paketti kuuluvat JEditorPane. Redigeerimise jaoks aga lihttekstikomponentidest piisab. Lisaks komponendis oleva teksti küsimisele ja määramisele saab eraldi küsida ja määrata märgistatud teksti, samuti kursori asukohta. Nimekiri (List) ning valik (Choice) lubavad kasutajal valida etteantud võimaluste seast. Nimekirjas on samaaegselt näha mitu rida. Kasutajal saab lasta märgistada samaaegselt ka mitu rida. Valiku puhul tuleb rippmenüü lahti vaid valimise ajaks, ülejäänud ajal on näha vaid üks, parasjagu valitud rida. Andmed nende ridadel on sõnedena, muul otstarbel kasutades tuleb neid vastavalt intepreteerida. Et saaks valida näiteks pilte või värve, tuleb kasutada swingi komponente või siis vastav komponent ise kokku panna. Meetodite abil saab nendel komponentidel lisada ja eemaldada ridu, küsida, millise numbri või millise sisuga rea on kasutaja märgistanud. Saab lasta ka automaatselt rida märgistada. Veidi sarnane on ka swingi komponent JTree, kus kasutaja saab samuti ridu märgistada, seal aga on andmed paigutatud hierarhiliselt, puuna. Silt (Label) võimaldab endasse paigutada ühe rea teksti. Swingi analoogile saab panna ridu mitu ning soovi korral ka pilte sisse. Nupp (Button) reageerib hiirevajutusele. Ka temale on võimalik awt-variandis panna üks rida teksti, swingi juures aga enam kujundada. Märkeruut (Checkbox) laseb kasutajal valida kahe võimaluse vahel (märgitud või mitte). Raadionupp (RadioButton) saab samuti olla kas sees või väljas, kuid neid kasutatakse enamasti juhul, kui saab valida vaid ühte mitme võimaluse seast. Selle eest hoolitsemiseks on vaja luua märkeruudugrupp (CheckBoxGroup), kes hoolitseb, et sinna gruppi lisatud nuppudest oleks vaid üks sisse lülitatud. Menüüriba (MenuBar) saab kinnitada raami külge. Temast saab panna hargnema menüüd ning neist omakorda alammenüüd. Popupmenüü võib panna välja hüppama mis tahes koha pealt. Joonistamise jaoks on loodud lõuend (Canvas), kuid vajaduse korral saab ka teistele komponentidele (näiteks rakendile) joonistada. Lõuendi abil saame suhteliselt kergesti omale soovitud komponendi luua. Näiteks kui soovime pildiga nuppu, mille pilt vajutamise ajal muutuks, siis tuleks luua lõuendi alamklass. Sinna saab kirjeldada, millist pilti näidata nupu üleval oleku ajal ning millist siis, kui hiirega tema peale vajutatakse. Kui komponent on suurem kui tema jaoks eraldatud ekraanipind, siis saab ta paigutada ScrollPane sisse, mille tulemusena saab kerimisribade abil komponenti liigutada, vaadates teda läbi tema jaoks loodud "akna". Paigutushaldurid Graafikakomponent tuleb ekraanil näitamiseks paigutada konteinerisse (nt. raam, paneel, rakend). Konteineri sees komponente paigutada aitavad paigutushaldurid. Nad hoolitsevad, et näiteks raami suuruse muutmisel komponendid ekraanil mõistlikus suuruses näha jääksid. Kuna iga konteiner on samaaegselt ka komponent (ehk tema alamklass), siis saab ka konteinereid endid paigutushaldurite abil paigutada. Niimoodi paneele (või muid konteinereid) sobivalt üksteise sisse paigutades on võimalik saavutada peaaegu igasugune soovitud tulemus. Mõnikord on võimalik ka täpselt ekraanipunktide järgi komponentide paigutus määrata, kuid see pole soovitav, sest näiteks raami suuruse või ekraani resolutsiooni muutmisel või uue komponendi lisamisel tuleks kogu kujundus uuesti kirjutada. Katsetamise ajal on lihtsaimaks paigustushalduriks FlowLayout. Seal pannakse komponendid üksteise järgi ritta ning kui rida täis saab, siis minnakse ekraanil järgmisesse ritta. GridLayout paigutuse puhul jagatakse konteineri juurde kuuluv piirkond ridadeks ja veergudeks, andes igale komponendile ühe lahtri. GridBagLayout on sarnane, kuid seal võib üks komponent katta ka mitu lahtrit. Swingi BoxLayout lubab komponendid panna kas ridadena või tulpadena, jättes nad loomulikku suurusse. BorderLayout lubab komponendid paigutada nelja serva ning keskele. Servas olevad komponendid jäetakse servaga risti olevat mõõdet pidi nende loomulikku suurusse, keskele pandud komponent venitatakse kogu ülejäänud pinna ulatuses välja. CardLayout võimaldab panna mitu kihti komponente üksteise peale, näidates välja pealmise kihi ning lubades kihte vahetada. Swingi JTabbedPane eesmärk on sarnane, kuid seal on kohe automaatselt juurde lisatud võimalus kasutajal soovitud kihti välja kutsuda. Kuularid Sündmuste tulemusena millegi juhtumiseks tuleb sündmuse registreerijalt (ehk allikalt) saata teade sündmuse vastuvõtjale. Ühe sündmuse (näiteks nupuvajutuse) teadet saab saata mitmele vastuvõtjale, muuhulgas ka iseendale. Et allikas vastuvõtjale teate saadaks, selleks peab olema vastuvõtja end allika juures registreerinud vastavat tüüpi sündmuse kuulajaks. Kuulajaks saab end registreerida isend, kelle klass realiseerib vastava sündmusetüübi kuulamiseks loodud liidest. Näiteks hiirevajutuse kuulamiseks peab klass realiseerima liidest MouseListener. Liidese realiseerimine aga tähendab seda, et klassis oleks kirjeldatud kõigi liideses deklareeritud meetodite käivitamisel tehtavad tegevused. Näiteks MouseListeneri realiseerijal peavad olema kõik liideses kirjeldatud meetodid: nii hiire vajutamise, ülestõstmise, komponendi piirkonda sisenemise kui komponendist väljumise kohta. Kui soovitakse, et mõnel juhul ei reageeritaks, siis tulebki arvutile selgeks teha, s.t. vastava meetodi sisuks kirjutada tühjad sulud. import java.awt.*; import java.awt.event.*; public class Hiir1{ public static void main(String argumendid[]){ Frame f=new Frame("Hiireraam"); f.setSize(200, 300); f.setVisible(true); f.addMouseListener(new HiireKuular1(f)); } } class HiireKuular1 implements MouseListener{ Frame raam; public HiireKuular1(Frame uusraam){ raam=uusraam; } public void mousePressed(MouseEvent e){ //suurendab raami laiust 10 ühiku võrra Dimension suurus=raam.getSize(); raam.setSize(suurus.width+10, suurus.height); } public void mouseReleased(MouseEvent e){} public void mouseClicked(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} } Et tühje sulge peaks vähem kirjutama, selleks on loodud adapterklassid liideste jaoks, mis defineerivad enam kui ühe meetodi. Vastava liidese adapterklass realiseerib liidest, jättes meetodi kehaks tühjad sulud, s.t. tehes meetodi käivitamisel mitte midagi. Kui me nüüd oma kuulari loome vastava adapteri alamklassina, siis piisab meil vaid üle katta meile vajalik meetod. Näiteks oma hiirekuulari loomisel katame üle vaid hiire vajutamisel käivitatava meetodi. Muude hiiresündmuste puhul kasutatakse adapterklassi meetodeid ning kuna seal midagi teha ei paluta, siis meile näib, nagu ei reageeritakski nendele sündmustele. import java.awt.*; import java.awt.event.*; public class Hiir2{ public static void main(String argumendid[]){ Frame f=new Frame("Hiireraam"); f.setSize(200, 300); f.setVisible(true); f.addMouseListener(new HiireKuular2(f)); } } class HiireKuular2 extends MouseAdapter{ Frame raam; public HiireKuular2(Frame uusraam){ raam=uusraam; } public void mousePressed(MouseEvent e){ Dimension suurus=raam.getSize(); raam.setSize(suurus.width+10, suurus.height); } } Komponent võib ka ise saata teateid enese juures juhtunud sündmustest ning neid siis töödelda. Sel juhul pole eraldi klassi (ega isendit) kuulari jaoks vaja luua. Piisab vaid, kui komponendi alamklass ise realiseerib vastava sündmuse kuulamiseks vajalikku liidest. Liidese realiseerimiseks peab aga meetodi keha olema kõikidel liideses kirjeldatud meetoditel. Sellest siin need tühjad meetodid, et programm teaks, et näiteks hiire sisenemise korral ei tule tal midagi teha. import java.awt.*; import java.awt.event.*; public class Hiir3 extends Frame implements MouseListener{ public Hiir3(){ addMouseListener(this); } public void mousePressed(MouseEvent e){ Dimension suurus=getSize(); setSize(suurus.width+10, suurus.height); } public void mouseReleased(MouseEvent e){} public void mouseClicked(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} public static void main(String argumendid[]){ Frame f=new Hiir3(); f.setSize(200, 300); f.setVisible(true); } } Kui aga ka komponendi alamklassis tahetakse kasutada adapteri võimalusi ning leitakse, et tühjade meetodite kirjutamine on aja ja ruumi raiskamine, siis tuleb veidi keerulisemalt hakkama saada. Kuna java keeles on võimalik pärida ainult ühelt eellaselt, siis üheaegselt nii Frame kui adapteri klassis paiknevat koodi pole võimalik pärida. Selgem ning kindlam on kirjutada eraldi adapteri alamklass ning ta konstruktorile anda parameetriks osuti komponendile nagu esimestes näidetes. Siis saab selle osuti kaudu komponendi tegevust juhtida. Versioonist 1.1 alates aga loodi võimalus sama probleemi ka lühemalt lahendada. Alljärgnevas näites luuakse adaptrile nimetu alamklass, kus kaetakse üle tema meetod, siis luuakse isend ning pannakse ta teateid kuulama. Meetod windowClosing kutsutakse välja siis, kui kasutaja vajutab raami sulgemise nupule. Selle tulemusena lõpetatakse programmi töö (System.exit). import java.awt.*; import java.awt.event.*; import java.awt.*; import java.awt.event.*; public class Raam4 extends Frame { public Raam4(){ addWindowListener( new WindowAdapter(){ public void windowClosing(WindowEvent e){ System.exit(0); } } ); } public static void main(String argumendid[]){ Frame f=new Raam4(); f.setSize(200, 300); f.setVisible(true); } } Mitmesugused kuularid Tekstikuular Tekstikuulari liideses on kirjeldatud üks meetod: textValueChanged. See meetod käivitatakse kuulajatel siis, kui allika (ehk tekstivälja või tekstiala) sees olev tekst on muutunud. Siin näites on rakend ühtlasi esimese tekstiala kuulariks. Kui esimeses tekstialas ehk allikas teksti muudetakse, siis selle tulemusena pannakse teise tekstiala sisuks esimese sisu koopia, kusjuures kõik tähed muudetakse väiketähtedeks. import java.applet.Applet; import java.awt.*; import java.awt.event.*; public class Tekstikuular extends Applet implements TextListener{ TextArea tekstiala1=new TextArea(3, 30); TextArea tekstiala2=new TextArea(3, 30); public Tekstikuular(){ init(); } public void init(){ add(tekstiala1); add(tekstiala2); tekstiala1.addTextListener(this); } public void textValueChanged(TextEvent e){ tekstiala2.setText(tekstiala1.getText().toLowerCase()); } } Klahvikuular Klahvikuular peab realiseerima liidest KeyListener ning temale saadetakse teated keyPressed, keyReleased ning keyTyped. Siin näites pööran tähelepanu vaid allavajutamise juhule. KeyEvent isendi meetod getKeyCode annab tulemuseks täisarvu, mis vastab klahvi koodile. Siis kontrollin, millise klahviga on tegemist ning toimin vastavalt sellele. Konstant KeyEvent.VK_LEFT tähendab näiteks noolt vasakule. Konstruktor kutsub välja init() meetodi ning alles viimases on kirjeldatud, mida tuleb teha uue isendi loomisel. Nii on see kirjutatud sellepärast, et rakendi loomisel konstruktorit ei kasutata, algkäsud isendile antakse meetodis init. Sama klassi aga iseseisva komponendina kasutades pannakse isendiloomel käima kõigepealt konstruktor. Käsklus repaint() keyPressed meetodi lõpus käsib ekraani ülejoonistada, s.t. kustutab vaja joonise ning käivitab siis eraldi lõimena meetodi paint. Sellise toimimise korral on ring õiges kohas ekraanil ka näiteks pärast rakendi nihutamist või teiste programmide alla sattumist. Ülekaetud meetod isFocusTraversable() on vajalik selleks, et komponent võiks sattuda klaviatuuri fookusesse. import java.applet.Applet; import java.awt.event.*; import java.awt.*; public class Klahvikuular extends Applet implements KeyListener{ int x=100, y=100; public Klahvikuular(){ init(); } public void init(){ addKeyListener(this); requestFocus(); } public void paint(Graphics g){ g.drawOval(x-10, y-10, 20, 20); } public boolean isFocusTraversable(){ return true; } public void keyPressed(KeyEvent e){ int kood=e.getKeyCode(); if(kood==KeyEvent.VK_LEFT)x--; if(kood==KeyEvent.VK_RIGHT)x++; if(kood==KeyEvent.VK_UP)y--; if(kood==KeyEvent.VK_DOWN)y++; repaint(); } public void keyReleased(KeyEvent e){} public void keyTyped(KeyEvent e){} } Fookusekuular Fookusekuular registreerib fookuse saabumise ning eemaldumise teated, s.t. nende sündmuste toimumisel käivitatakse vastavad meetodid. Siin näites lihtsalt omistatakse tõeväärtusmuutujale väärtus tõene või väär vastavalt sellele, kas rakend on fookuses või mitte. Seejärel palutakse ekraan üle joonistada. Ülejoonistamine sõltub muutuja väärtusest. Kui komponent on fookuses, siis joonistatakse esiplaanivärviga rakend üle. Kui aga komponent fookuses pole, siis jäetakse pind värvimata. import java.applet.Applet; import java.awt.event.*; import java.awt.Graphics; public class Fookusekuular extends Applet implements FocusListener{ boolean fookuses=true; public Fookusekuular(){ init(); } public void init(){ addFocusListener(this); } public void paint(Graphics g){ if(fookuses){ g.drawRect(0, 0, getSize().width-1, getSize().height-1); } } public boolean isFocusTraversable(){ return true; } public void focusGained(FocusEvent e){ fookuses=true; repaint(); } public void focusLost(FocusEvent e){ fookuses=false; repaint(); } } Hiirekuular Hiire vajutuste ning hiire liikumise registreerimiseks on kummagi jaoks omaette kuular. Nii on kasulik seetõttu, et hiire liigutamisel tuleb teateid tunduvalt rohkem kui vajutamisel. Liigutamisel tuleb registreerida piisavalt palju punkte, et nende abil oleks võimalik kõverjoonelist liikumisteed ette kujutada. Järgnevas näites joonistatakse hiire vajutamise kohale punane ring, ülestõstmise kohale sinine ning kuid hiir väljub komponendi piirkonnast, siis joonistatakse viimase pind valge värviga üle. Vajutamise juures kirjutatakse ka, mitu korda selles punktis on hiireklahvi lühikeste vahedega vajutatud. Andmed selle kohta saab hiirevajutussündmuse puhul väljakutsutava meetodi MouseEvent tüüpi parameetrist. import java.applet.Applet; import java.awt.*; import java.awt.event.*; public class Hiirekuular extends Applet implements MouseListener{ public Hiirekuular(){ init(); } public void init(){ addMouseListener(this); } public void mousePressed(MouseEvent e){ Graphics g=getGraphics(); g.setColor(Color.white); g.fillRect(e.getX()-15, e.getY()-15, 30, 30); g.setColor(Color.red); g.drawOval(e.getX()-15, e.getY()-15, 30, 30); g.drawString(e.getClickCount()+"",e.getX(), e.getY()); } public void mouseReleased(MouseEvent e){ Graphics g=getGraphics(); g.setColor(Color.blue); g.drawOval(e.getX()-5, e.getY()-5, 10, 10); } public void mouseExited(MouseEvent e){ Graphics g=getGraphics(); g.setColor(Color.white); g.fillRect(0, 0, getSize().width, getSize().height); } public void mouseEntered(MouseEvent e){} public void mouseClicked(MouseEvent e){} } Hiire liikumise kuular Pildi joonistamiseks on vaja registreerida nii hiire vajutusi kui liikumist. Et pääseda hiirekuulari praeguses programmis mitte vaja minevate sündmuste töötlemisest, selleks loon oma kuulari klassi MouseAdapter alamklassina, mis samaaegselt realiseerib MouseMotionListener liidest (meeldetuletuseks: korraga võib laiendada vaid ühte klassi, kuid liideseid realiseerida kuitahes mitu). Hiire allavajutamisel jäetakse meelde vajutuse asukoht. Liigutamisel aga tõmmatakse joon eelmise punkti ning hetkel hiire asukohaks oleva punkti vahele. import java.applet.Applet; import java.awt.*; import java.awt.event.*; public class Joonistus extends Applet{ public Joonistus(){ init(); } public void init(){ JoonistuseKuular kuular=new JoonistuseKuular(this); addMouseListener(kuular); addMouseMotionListener(kuular); } } class JoonistuseKuular extends MouseAdapter implements MouseMotionListener{ Joonistus peremees; int vanax, vanay, uusx, uusy; public JoonistuseKuular(Joonistus j){ peremees=j; } public void mousePressed(MouseEvent e){ vanax=e.getX(); vanay=e.getY(); } public void mouseDragged(MouseEvent e){ uusx=e.getX(); uusy=e.getY(); Graphics g=peremees.getGraphics(); g.drawLine(vanax, vanay, uusx, uusy); vanax=uusx; vanay=uusy; } public void mouseMoved(MouseEvent e){} } Järgnev näide erineb eelmisest selle poolest, et pilt joonistatakse enne mällu ning alles sealt ekraanile. Sellisel juhul saab pildi ka pärast selle ekraanilt kadumist uuesti sinna tekitada. Pilt luuakse mällu paint-meetodi esmakordsel väljakutsel. Meetod update on üle kaetud, et joonistamine ilusamini välja näeks. Vaikimisi update enne joonistab komponendi taustavärviga üle ning alles siis joonistab sinna peale pildi. Kui aga paluda update'l kohe pilt joonistada, siis aitab see vältida vilkumist. import java.applet.Applet; import java.awt.*; import java.awt.event.*; public class Joonistus2 extends Applet{ Image pilt; public Joonistus2(){ init(); } public void init(){ Joonistuse2Kuular kuular=new Joonistuse2Kuular(this); addMouseListener(kuular); addMouseMotionListener(kuular); } public void paint(Graphics g){ if(pilt==null)pilt=createImage(getSize().height, getSize().width); g.drawImage(pilt, 0, 0, this); } public void update(Graphics g){ paint(g); } } class Joonistuse2Kuular extends MouseAdapter implements MouseMotionListener{ Joonistus2 peremees; int vanax, vanay, uusx, uusy; public Joonistuse2Kuular(Joonistus2 j){ peremees=j; } public void mousePressed(MouseEvent e){ vanax=e.getX(); vanay=e.getY(); } public void mouseDragged(MouseEvent e){ uusx=e.getX(); uusy=e.getY(); Graphics g=peremees.pilt.getGraphics(); g.drawLine(vanax, vanay, uusx, uusy); vanax=uusx; vanay=uusy; peremees.repaint(); } public void mouseMoved(MouseEvent e){} } Kokkuvõte Graafikakomponendid aitavad lihtsustada kasutajaga suhtlemist. Kümmekonda awt-paketis olevat komponenti juhitakse operatsioonisüsteemi poolt, nad näevad välja nii nagu vastavas operatsioonisüsteemis tavaks. Mõnikümmend swing-komponenti lisavad võimalusi. Need näevad välja igal pool ühtemoodi, töötavad suhteliselt aeglasemalt, kuid neid on kergem pilkupüüdvaks kujundada. Komponentidega juhtunud sündmuste töötlemiseks tuleb luua vastava sündmuse kuular ning kuularil lasta end sündmuse allika juures registreerida. Komponent võib ka ise olla enesega juhtunud sündmuste kuulariks. Lisaks kuularitele saab vajadusel ka uusi sündmusi ja komponente ise luua.