Johdanto. Luku 1. jälkeen. Lähes kaikki nykyisin Unix-tyyppisten käyttöjärjestelmien päällä ajettavat graafiset käyttöliittymät pohjautuvat

Koko: px
Aloita esitys sivulta:

Download "Johdanto. Luku 1. jälkeen. Lähes kaikki nykyisin Unix-tyyppisten käyttöjärjestelmien päällä ajettavat graafiset käyttöliittymät pohjautuvat"

Transkriptio

1 Luku 1 Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään moderniin asiakokonaisuuteen: graafiseen käyttöliittymään, erityisesti sen muodostamiseen sovellukselle. Graafisen käyttöliittymän toteuttamiseen liittyvät rakenteet eivät ole osa ohjelmointikieltä, vaan ne esiintyvät kielen yhteydessä kirjastojen kautta. Perustilassaan sovelluksen graafinen käyttöliittymä odottaa käyttäjältä ärsykkeitä (tapahtumia), joihin reagoida. Tämän toteuttaminen nojautuu rinnakkaisuuden 1 heikompaan muotoon: samanaikaisuuteen säikeitä käyttäen ja 1970-luvuilla (ja laajasti myöhemminkin) ohjelmointi on tarkoittanut lähinnä keskusmuistissa olevan (ja tiedostoista keskusmuistiin tuodun) tiedon käsittelyä algoritmisesti. Ohjelmia käytettiin tuolloin eräajon kautta tai alkeellisen riviorientoituneen käyttöliittymän ((paperi)pääte) kautta. Tietokoneita käytettiin silloin lähinnä eräajosovelluksiin, kuten palkanlaskennan tekemiseen. Vuorovaikutteinen käyttö ja sitä kautta graafinen käyttöliittymä tuli merkittäväksi vasta paljon myöhemmin. Kunnian graafisten käyttöliittymien keksimisestä mielletään yleisesti kuuluvan Xeroxin (Palo Alto) tutkimuslaboratorioille (1970-luvun loppu), mutta ensimmäinen graafinen käyttöliittymä oli Applen Lisa-merkkisessä 2 tietokoneessa (n. vuonna 1983). Graafiset käyttöliittymät yleistyivät sittemmin Macintosh-tietokoneiden MacOS-käyttöjärjestelmän, tavallisten PC-koneiden Windowskäyttöjärjestelmän (MS) ja X 3 Window-pohjaisten käyttöliittymien (X11, Unix) myötä. Nykyisin käyttöjärjestelmät ovat lähes poikkeuksetta graafisia ja niin myös niiden avulla suoritettavilla ohjelmilla on tyypillisesti graafinen käyttöliittymä. Graafisuuden merkitystä ohjelmoinnissa on edelleen nostanut WWW-selaimien mahdollistama yleinen alusta ohjelmille ja selaimiin liittyvä tuki graafisuudelle. 1 Rinnakkaisuus tarkoittaa usean kontrollivirran suorittamista yht aikaa usean prosessorin toimesta. Samanaikaisuus on rinnakkaisuuden heikompi muoto, jossa vähintäänkin näennäisesti samaanaikaan suoritetaan useita käskyvirtoja käytännössä samanaikaisuus usein toteutetaan yhden laskentayksikön avulla lomittamalla jotenkin käskyvirtojen suoritusta. Javan säikeet perustuvat samanaikaisuuteen. Aidosti rinnakkaisuuteen perustuvia kieliäkin on niiden voima tyypillisesti perustuu suoritettavien käskyvirtojen tiukasti synkroniseen suoritukseen. Usein rinnakkaisuus saavutetaan ohjelmissa ilman rinnakkaisuuskonstruktioita kielen tasolla; esim. MPI-kirjaston avulla. 2 Jostain syystä Xerox:ssa väheksyttiin graafisen käyttöliittymän merkitystä. Lisan käyttöliittymän syntymisestä on paljon kertomuksia ja yleensä niissä mainitaan myös se, että Applen (eräs) perustaja Steve Jobs kävi vierailulla Xerox:n laboratorioissa vuonna 1979 ja ihastui välittömästi graafiseen käyttöliittymään. Lisan jälkeen Macintosh-merkkiset tietokoneet jatkoivat graafisten käyttöliittymien voittokulkua Macit olivat tunnettuja siitä, että niissä ei ollut tavallista tekstipohjaista käyttöliittymää... 3 Kunnia X Window:n kehittämisestä annetaan usein MIT:lle. Ensimmäiset versiot valmistuivat hieman 1980-luvun puolivälin jälkeen. Lähes kaikki nykyisin Unix-tyyppisten käyttöjärjestelmien päällä ajettavat graafiset käyttöliittymät pohjautuvat X:ään. 1

2 2 LUKU 1. JOHDANTO 1.1 Rakenne Seuraavaksi tässä oppimateriaalissa tutustutaan AWT:hen (Abstract Windowing Toolkit) ja Swingkokonaisuuteen. Tutustuminen aloitetaan AWT:n perusteista taustaa, johdatus komponentteihin, GUI-sovellusten (GUI= Graphical User Interface) yleinen rakenne, johdatus tapahtumankäsittelyyn luvussa 2. Tämän jälkeen tutustutaan muutamiin AWT:n graafisiin peruskomponentteihin luvussa 3. Tapahtumankäsittelyn ymmärtäminen on hyvin keskeistä GUI-sovellusten ohjelmoinnin kannalta. Tätä asiaa käsitellään perusteellisesti luvussa 4. Loput AWT:n graafisista komponenteista ja muutamat keskeiset apuluokat käsitellään luvussa 5. Ennen siirtymistä Swing-kokonaisuuden käsittelyyn, luvussa 6 pohditaan erilaisia tapoja tehdä GUI-sovellus ja annetaan yleisiä neuvoja sovelluksen tekemiseksi. AWT-osuuden jälkeen siirrytään käsittelemään AWT:tä osittain syrjäyttämään suunniteltua Swingkokonaisuutta (joka tuli JDK 1.2:n myötä 4 ). Swing on monessa mielessä parempi kuin AWT, mutta kaikki sovellusalustat eivät välttämättä tue Swing:ä (ja lisäksi Swing pohjautuu AWT:hen). Johdatus Swing-kokonaisuuteen tehdään luvussa 7. Swing-komponentteja on hieman AWT-komponentteja enemmän ja lisäksi Swing:iin liittyy suurehko joukko hyödyllisiä apuluokkia. Näitä käydään läpi luvussa 8. Tässä vaiheessa GUI-sovellusten tekemiseen liittyvien asioiden pitäisi olla varsin selviä. Toiminnallisuutta GUI-sovelluksiin saadaan kahdella tapaa: tapahtumankäsittelyn avulla ja itsenäisten säikeiden avulla (animointi). Luvussa 9.1 kerrotaan säikeiden muodostamiseen ja käyttämiseen liittyvät yleisen asiat. Luku 9 puolestaan pohtii säikeiden käyttöä GUI-sovellusten yhteydessä asiakkaan kannalta (animointia, verkkoyhteydet). Javassahan on synchronized avainsana, jonka avulla voidaan estää säikeitä käsittelemästä esim. tiettyjä muuttujia samanaikaisesti. Vaikka säikeet eivät olekaan sama asia kuin rinnakkaisuus 5, niin tarkastelemalla samanaikaisuuteen liittyvää problematiikkaa, tarkastellaan samantapaisia ongelmia kuin mitä liittyy aitoihin rinnakkaisohjelmointikieliin. Luvuissa 10 tarkastellaan lyhyesti sovellusten tekemistä muihin kuin Javan AWT- ja Swing-kirjastoihin pohjautuen. Kohteina kyseisessä luvussa ovat SWT/JFace, Windows (Win32 API, MFC, C# ja Windows Forms) ja ylipäänsä web-selaimeen liittyvät GUI-tekniikat ja Ajax-tekniikat. Luvussa 11 luonnehditaan hieman ohjelmien tuottamista sovelluskehittimien avulla. Graafisten käyttöliittymäsovellusten testausta ja dokumentointia tarkastellaan luvussa 12. Loppusanat esitetään luvussa Itse asiassa Swing tuli jo hieman ennen JDK 1.2:ta, osana ns. Java Foundation Classes-kokonaisuutta (JFC). 5 Rinnakkaisuus ei nykyään vielä ole osa perusohjelmointia. Kirjallisuudessa on kuitenkin esitetty hyvin perusteltuja kannanottoja sen puolesta, että ohjelmointia kuuluisi oikeastaan opettaa rinnakkaisohjelmointina peräkkäisohjelmoinnin sijaan. Syynä tähän on, että ongelmien ratkaisujen esittäminen rinnakkaisuuden avulla on luonnollisempaa kuin laskennan pakottaminen johonkin mahdollisista peräkkäisistä suoritusjärjestyksistä tällaisia järjestyksiähän on usein hyvin monia (mielivaltaisesti valittu peräkkäinen järjestys vaikeuttaa ohjelman merkityksen ymmärtämistä). Toinen rinnakkaisohjelmoinnin opettamista puoltava seikka on rinnakkaisuuden yleistyminen tietokoneissa. Varsin monissa tietokoneissa (ns. PC-koneissakin) on useita prosessoriytimiä. Lisäksi (transistorien) pakkaustekniikka on kehittynyt viime aikoina huimasti: Pentium-prosessorien toteuttaminen on vaatinut n miljoonaa transistoria, mutta pakkaustekniikan kehittyminen mahdollistaa monin(kymmen)kertaisen transistorimäärän sijoittamisen yhden prosessorin alalle. Tätä ylimääräistä tilaa voi käyttää kahdella tavalla: muistina tai lisäämällä prosessorin rinnakkaisuutta. Uusimmat prosessorit sisältävät lähes poikkeuksetta rinnakkaista suoritusta tukevia piirteitä.

3 Luku 2 AWT:n perusteet AWT (Abstract Windowing Toolkit) on JDK:n useasta paketista koostuva kirjastokokonaisuus graafisten käyttöliittymien muodostamiseen. AWT oli mukana jo JDK 1.0:ssa, mutta nykyisen muotonsa (suurin piirtein) se sai JDK 1.1:n myötä. Erityisesti siirryttäessä versioon 1.1 tuli paljon muutoksia GUI:n toiminnan kannalta keskeiseen tapahtumankäsittelyyn. AWT koostuu tapahtumankäsittelyn lisäksi graafisista komponenteista (luokkia, jonka mukaisilla olioilla on graafinen ulkoasu), komponentien hierarkiaan ja ikkunointiin liittyvistä luokista sekä useista apuluokista, joilla kuvataan erilaisia yksityiskohtia, kuten fontit, värit, komponenttien sijoittelu, jne. Tavallaan AWT 1 on jo myös hieman vanhentunut, sillä JDK 1.2:n (tai oikeammin Java Foundation Classes, JFC) myötä tuli Swing-kokonaisuus 2, jonka avulla voidaan tehdä tietyssä mielessä kevyempiä käyttöliittymiä. Lisäksi Swing tarjoaa AWT:tä enemmän graafisia komponentteja ja Swingin komponentit ovat AWT:n vastaaviin nähden monipuolisempia. Toisaalta, Swingin kokonaisuuden hallinta (ikkunointi korkeimmalla tasolla) perustuu edelleen AWT-komponentteihin ja kaikki suoritusalustat (esim. selaimet) eivät tue JDK 1.2:ta tai uudempia versioita, joten graafisten käyttöliittymien tekemiseen perehtyminen kannattaa edelleen aloittaa AWT:stä. Versioiden myötä on tullut vain pieniä lisäyksiä. 2.1 AWT ja sovellukset AWT:llä voi tehdä graafisia sovelluksia ja appletteja (eli sovelmia). Appletit ovat luokkia, jotka perivät java.applet.applet luokasta. Appletit on tarkoitettu suoritettavaksi WWW-selaimen toimesta. Graafiset sovellukset puolestaan ovat kuten muutkin Java-sovellukset. Niillä on main -metodi, jota suorittavan tahon (tulkki java) toimesta kutsutaan. Graafisuus perustuu luokan java.awt.frame käytöön 3. AWT-sovelluksissa ja -appleteissa ohjelmoija luo yhden tai useamman ikkunaolion ja sijoittaa kyseisiin ikkunaolioihin graafisia komponentteja, kuten painikkeita, tekstikenttiä ja valitsimia. Itse asiassa päätason ikkunaoliot ovat komponettisäiliöitä, joihin voidaan sijoitella yksinkertaisempia graafisia olioita. Applettien tapauksessa luokka Applet edustaa graafisten komponettien säiliötä, mutta se tarjoaa myös tärkeän rajapinnan selaimille (mm. metodit init, start, stop, update, joita 1 Sunin kerrotaan tehneen AWT:n kuudessa viikossa... 2 Jatkossa puhutaan myös Swing-paketista, vaikka Swing ei ihan tarkkaan ottaen ole Javan paketti (package). 3 Luonnollisesti käyttö edellyttää myös, että suoritusympäristössä on käytettävissä graafinen esityslaite (monitori, tms). Windows:n tapauksessa sellainen on luonnollisesti käytettävissä. Unix-ympäristössä tilanne voi olla toinen: jos ollaan tavallisen pääteyhtyden varassa, suoritusympäristö (komentotulkki) ei välttämättä tue graafisuutta. X:n tapauksessa Unix-kuoren DISPLAY-ympäristömuuttujalla tulee olla oikea arvo. 3

4 4 LUKU 2. AWT:N PERUSTEET selain kutsuu tietyissä tilanteissa). AWT-sovelluksissa ei kuitenkaan pelkästään määritellä, mitä komponentteja mihinkin ikkunaan sijoitetaan, vaan erityisillä tapahtumankäsittelyolioilla kerrotaan, miten graafisten komponenttien tulisi reagoida käyttäjän aiheuttamiin tapahtumiin. Tällaisia tapahtumia ovat mm. hiiren liikuttaminen, klikkaaminen ja jonkin näppäimistön merkin painaminen. Kullakin AWT:n graafisella komponentilla on jokin ulkoasu, johon voi itsekin vaikuttaa. Erityisesti komponentteihin liittyy paint -metodi, joka tietää, miten komponentin graafinen ulkoasu tuotetaan. Java on käyttöjärjestelmäriippumaton miten siis graafiset komponentit eri käyttöjärjestelmien yhteydessä tietävät, miten niiden ulkoasu esitetään? Käytännössä AWT on toteutettu erikseen kunkin käyttöjärjestelmän alustalle nojautuen niiden tarjoamaan grafiikkakirjastoon. Itse asiassa AWT-komponenttien esitystapa on hieman erilainen eri käyttöjärjestelmissä. Käytännössä toteuttaminen on tehty niin, että kutakin AWT:n komponenttia kohti luodaan kyseisen ikkunointijärjestelmän mukainen graafinen komponentti, ns. vastinolio (peer object), joka suorittaa varsinaisen graafisen esityksen. AWT:n komponentteja kutsutaan raskaiksi komponenteiksi juuri tästä syystä AWT:n käyttäminen varaa AWT-komponenttien lisäksi suhteellisen paljon tilaa ikkunointijärjestelmän käyttämän esityskirjaston mukaisille komponenteille, joita käytetään toteuttamaan itse grafiikka. Tämä yhteys edustavien AWT-komponenttien ja grafiikan toteuttavien komponenttien välillä toteutetaan luokan java.awt.toolkit avulla, mutta sen yksityiskohdista käyttäjän ei tarvitse tietää mitään. Vaikka AWT:n komponenteilla onkin kiinteä yhteys esitystapaan, ohjelmoijan kannalta AWT on käyttöjärjestelmästä riippumaton (kuten Java yleensäkin) siis ohjelmoijan ei tarvitse tietää mitään siitä, miten ikkunointijärjestelmän mukaisessa ympäristössä AWT on toteutettu, vaan ohjelmoijalla on aina käytettävissään sama kirjastojen muodostama ohjelmointirajapinta (API = Application Programming Interface). Koska AWT on alunperin suunniteltu tukemaan MacOS:ää, Windows:a ja X:ää, AWT on pikemminkin leikkaus kuin unioni kyseisten ikkunointijärjestelmien tarjoamista ominaisuuksista AWT:n komponenttien hierarkia AWT:n GUI-komponenttien juuri on luokka Component. Monimutkaisemmat ikkunakomponentit kuten myös graafiset peruskomponentit ovat tämän luokan välittömiä tai välillisiä aliluokkia. Jos halutaan tehdä itse uusia AWT-komponentteja, niin niidenkin tulee olla Component:n aliluokkia. Kuvassa 2.1 on AWT-komponenttien perimyssuhteet esitetty siten, että isäluokka on oikealla puolella. Kuvan mukaisilla luokilla on muitakin tunnettuja aliluokkia, mutta muut tunnetut luokat kuuluvat Swing-pakettiin. Luokka Applet sijaitsee paketissa java.applet, kun muut kuvan 2.1 luokat sijaitsevat paketissa java.awt. Seuraavassa lyhyt selostus kuvan luokista: Component Graafisten komponenttien perusluokka. Määrittelee graafisten komponenttien perusominaisuudet (n. 150 metodia + n. 50 vanhentunutta metodia). Kaikkiin komponentteihin liittyy mm. metodi void paint(graphics) ja sitä kautta tieto niiden ulkoasusta. Luku 3.1. Button Yksinkertainen painike. Luku 3.3. Canvas Piirtokangas. Luku 3.4. Checkbox Valintalaatikko. Luku 5.7. Choice Pudotusvalikko. Luku 3.5. Container Komponenttien varasto ideana on, että komponenttien joukko muodostaa myös komponentin, jolla on graafinen ulkoasu. Tällä luokalla on useita aliluokkia: erikoistuneenpia säiliöitä. Luku 5.1.

5 2.1. AWT JA SOVELLUKSET 5 Button Canvas CheckBox Choice Panel Applet Component Container Label List ScrollPane Window Dialog Frame FileDialog Scrollbar TextComponent TextArea TextField Kuva 2.1: AWT:n komponenttien perimyssuhteet. Label Nimike eli yksinkertaisesti tekstiä. Luku 3.2. List Vyörytyslista. Luku 5.6. Scrollbar Liukuvalitsin. Luku 5.5. TextComponent Tekstikomponentti, jolla kaksi muotoa: yksi- tai monirivinen. Luku 3.6. TextArea Monirivinen tekstialue, johon käyttäjä voi syöttää tekstiä. Luku 3.6. TextField Yksirivinen tekstikenttä, jonka sisällön käyttäjä voi täyttää. Luku 3.6. Panel Paneeli, johon voi taltioida muita komponentteja kuten Container:iinkin. Paneeliin liittyy oletusarvoisesti FlowLayout-komponenttien sijoittelija. Sijoittelutyyliä voi vaihtaa. Paneeli voi olla osana jotain toista paneelia (tai muuta ikkunaa). Luku 5.3. Applet Appletti, eli sovelma, on komponenttien säiliö, joka osaa toimia yhteen selainten kanssa. Erityisesti selain kutsuu tietyssä vaiheessa appletin init -, stop -, start -, ja update -metodeja. Usein appletteja käytetään sijoittelematta komponentteja siihen, vaan vain piirretään appletin piirtoalueelle. Applettiolion graafinen ulkoasu määritellään paint -metodilla, mutta applettiin sijoitetut graafiset komponentit piirtyvät appletin piirtoalueen päälle. Applettien eräs hallitseva ominaisuus on niiden verkkosuoritusympäristön rajoittuneisuus. ScrollPane Paneelin tapainen komponenttien säiliö, johon kuitenkin liittyy kiinteä koko X- ja Y- suunnassa. Tähän säiliöön liittyvät alueen liu uttimet X- ja/tai Y-suunnassa, jos säiliöön sijoitetut komponentit vaativat enemmän tilaa X- ja/tai Y-suunnassa kuin mitä säiliön esittämiseen on varattu. Window Ikkuna on päätason säiliöluokka, johon ei liity reunoja eikä myös mahdollisuutta liittää siihen menuvalikoita. Luku 5.2.

6 6 LUKU 2. AWT:N PERUSTEET Dialog Dialogi on luokka, jonka avulla käyttäjälle esitetään yksinkertaisia ikkunoita, joiden tarkoituksena on kuitata jokin asia. Esimerkiksi käyttäjälle voidaan kirjoittaa jokin tiedoitus, jonka lukemisen käyttäjän halutaan kuittaavan. Yleisemmin voidaan esittää jokin kysymys, johon on useita vastausvaihtoehtoja. Luku 5.8. FileDialog Tiedostodialogi on monipuolinen työkalu tiedostojen (nimien) valitsemiseen. Luku 5.9. Frame Lopuksi, kehys (frame) on säiliöluokka, johon liittyy kehysreuna ja mahdollisesti menuvalikoita. Frame on GUI-sovellusten perusluokka graafiset sovellukset luovat yhden tai useamman kehyksen, ja sijoittelevat komponentteja siihen (niihin). Edellisten lisäksi AWT-sovelluksissa käytetään tapahtumankäsittelijöitä (n. 20 luokkaa, luku 4), tapahtumia (n. 10 luokkaa, luku 4), erilaisia säiliöiden komponenttien sijoittelun määrääviä layoutluokkia (n. 5 luokkaa, luku 5.4), menuvalikoita (luku 5.10) ja ns. popup-valikoita ( ponnahdusvalikko, popup menu, luku 5.11) sekä niiden osia esittäviä luokkia (n. 5 kpl). Edellisten lisäksi AWT:hen liittyy suuri määrä sekalaisia apuluokkia. 2.2 Yksinkertaisen GUI-sovelluksen resepti Yksinkertaisimmillaan GUI-sovellus (ei siis appletti) muodostuu ohjelmasta, jossa tehdään yksi Frameluokan mukainen olio. Kyseiseen olioon sijoitetaan yksinkertaisia graafisia komponentteja sekä mahdollisesti monimutkaisempia rakenteellisia säiliökomponentteja. Komponentit ovat olioita, joihin liittyy graafinen ulkoasu. Kuhunkin säiliökomponenttiin tyypillisesti liitetään säiliön komponenttien sijoittelun määräävä sijoittelumanageri (layout manager), jonka on jonkin layout-luokan mukainen olio. Lisäksi luodaan joitakin tapahtumankäsittelijäolioita, joita liitetään GUI-komponentteihin. Aivan lopuksi luodun graafisen oliokokonaisuuden käsketään myös näkyä graafisesti. Hieman yksinkertaistaen voidaan sanoa, että GUI-sovelluksen main -metodissa (ja siitä kutsuttavissa metodeissa) ei sitten juuri muuta tehdäkään luodaan vain graafisia olioita ja niihin liitettäviä tapahtumankäsittelyolioita tätä kutsutaan graafisen käyttöliittymän alustusvaiheeksi. Erityisesti, main -metodin lopussa ei ole silmukkaa, jossa ryhdyttäisiin kuuntelemaan käyttäjän sovellukseen kohdistamia tapahtumia ja reagoitaisiin sitten jollakin tavalla kyseisiin tapahtumiin. GUI-sovelluksen main -metodin lopussa graafista kokonaisuutta tyypillisesti käsketään muodostamaan graafinen esitys. Tämän jälkeen main -metodi loppuu. Kutsuttaessa sovellusta käyttöjärjestelmätasolta suoritetaan vain main -metodi eikö siis main -metodin loppuun tultaessa sovelluksen kuuluisi vapauttaa kaikkien olioiden varaama tila ja päättää itse sovellus? Näin ei tapahdu, vaikka tietyssä mielessä GUI-sovellus on suoritettu loppuun. Syynä tähän ilmiöön on, että graafisen käyttöliittymän luominen luo samalla erillisen säikeen 4, jonka tehtävänä on prosessoida käyttöliittymään kohdistuvat tapahtumat. Toisaalta Java-sovellus ei lopu ennen kuin kaikki siihen liittyvät (normaalit, ei tausta)säikeet ovat lopettaneet toimintansa. GUI-sovelluksen luomisen seurauksena syntyy siis erillinen säie, jonka tehtävänä on odottaa ja ottaa vastaan tapahtumia (event) käyttöjärjestelmätason graafisilta vastinolioita, tunnistaa mihin AWT:n kohdeolioon (event source) kyseinen tapahtuma kohdistuu, ja laukaista (fire) kyseisen kohdeolion 4 Miten tämän tarkkaan ottaen tapahtuu, vaikuttaa olevan hyvin varjeltu salaisuus. Suurehko kirjajoukko ei tunnu osaavan antaa vastausta siihen, missä ja minkä toimesta kyseinen säie luodaan. Vaihtoehtoja lienee kaksi: joko GUI-sovelluksen pääikkunan (Frame-olion) luonti luo sen tai sitten kyseinen säie liittyy jotenkin GUI-sovelluksen päätason toteuttavaan vastinolioon (peer)...

7 2.3. ESIMERKKEJÄ 7 sellaiset tapahtumankäsittelijät, jotka voivat käsitellä kyseisen tapahtuman. Kohdeoliohin (siis graafisiin komponentteihin) on voinut kiinnittyä erilaisiin tapahtumiin reagoivia tapahtumankäsittelijöitä. Tapahtumankäsittelijät ovat yksinkertaisesti olioita, joiden ainoa tehtävä on osata suorittaa tietty metodi tai tietyt metodit. Käyttöjärjestelmän kannalta tapahtumankäsittelijät edustavat ns. call back -funktioita. Tapahtumankäsittelijöitä kutsutaan myös tapahtumankuuntelijoiksi (event listener). Huomaa, että yksi tapahtuma voi laukaista useita tapahtumankäsittelijöitä. Lisäksi tapahtuma saattaa saada komponentin laukaisemaan jonkin tapahtuman. Edellisen perusteella pitäisi olla ymmärrettävää, että Javan tapahtumankäsittelymallia kutsutaan delegointiin perustuvaksi. Yleisesti tällaista tapahtumankäsittelijöiden muodostamiseen perustuvaa ohjelmointia kutsutaan tapahtumaohjatuksi ohjelmoinniksi (event-driven programming). Tapahtumat ovat jonkin EventObject-luokan 5 (AWT:n tapauksessa AWTEvent-luokan (joka on EventObject:n aliluokka)) aliluokan mukaisia olioita. Tällaisen aliluokan nimi on muotoa XXXEvent, esim. MouseEvent tai KeyEvent. Tapahtumankäsittelijät puolestaan ovat jonkin EventListenerluokan aliluokan mukaisia olioita. Tapahtumankäsittelyluokat tehdään sovelluksessa. Ne perustuvat (periytyminen) luokiin, joiden nimi on usein muotoa XXXListener (tai XXXAdapder), esim. Mouse- Listener, KeyListener tai KeyAdapter. Tapahtumankäsittelyä käsitellään tarkemmin luvussa Esimerkkejä Seuraavassa kolme esimerkkiä graafisista sovelluksista: 2 applettia ja 2 itsenäistä GUI-sovellusta HiWorld-appletti Esimerkissä 2.1 on klassinen tervehdyksen tulostava appletti. Appletti lukee tulostettavan tervehdyksen HTML-tiedostosta PARAM-määren välityksellä, tulostaa sen keltaista taustaa vasten keskitetysti ja piirtää vielä lopuksi kaksi ellipsiä tekstin ympäri. Ks. kuva 2.2. Kuva 2.2: Appletin HiWorldApplet suorittaminen appletviewer:lla. Alla esimerkki käynnistävän HTML-tiedoston sisällöstä. Esimerkin 2.1 appletti edustaa hädin tuskin graafista käyttöliittymää, sillä siihen ei liity muuta toiminnallisuutta kuin tervehdyksen tulostami- 5 On olemassa myös luokka Event, mutta se liittyy JDK 1.0:aan.

8 8 LUKU 2. AWT:N PERUSTEET nen. Lisäksi, appletti ei käytä mitään GUI-komponentteja, vaan graafisuus saadaan aikaan piirtämällä suoraan appletille varatulle graafiselle alueelle (sen taustapaperille ). <html> <head> <title>hiworldapplet</title> </head> <body> <APPLET CODE=HiWorldApplet WIDTH=300 HEIGHT=300> <PARAM NAME=tervehdys VALUE="Hi there, world!"> </APPLET> </body> </html> Esimerkki 2.1 Appletti, joka tulostaa tervehdyksen. import java.applet. ; import java.awt. ; public class HiWorldApplet extends Applet { private String tervehdys; public void init() { tervehdys = getparameter("tervehdys"); if (tervehdys == null) tervehdys = "Hi World!"; public void paint(graphics g) { int x = g.getclipbounds().width; int y = g.getclipbounds().height; g.setcolor(color.yellow); g.fillrect(0,0,x,y); x = x/2; y = y/2; FontMetrics fm = g.getfontmetrics(g.getfont()); int tekstinleveys = fm.stringwidth(tervehdys); int tekstinkorkeus = fm.getheight(); x = x - tekstinleveys/2; y = y - tekstinkorkeus/2; g.setcolor(color.blue); g.drawstring(tervehdys, x, y); g.setcolor(color.red); g.drawoval(x-15,y-(int)(1.3 tekstinkorkeus), tekstinleveys+30,2 tekstinkorkeus); g.drawoval(x-20,y-(int)(1.3 tekstinkorkeus)-5, tekstinleveys+40,2 tekstinkorkeus+10); // end class HiWorldApplet

9 2.3. ESIMERKKEJÄ TODO-lista Seuraava esimerkki 2.2 on GUI-sovellus, joka muodostaa yhden kehysolion ja sijoittelee siihen kaksi GUI-komponenttia: päiväyksen sisältävän tekstin ja pudotusvalikon, joka edustaa ToDo -asioiden listaa. Tämän listan sisältö haetaan ohjelmassa tiedostosta. Kuva 2.3 näyttää, miltä sovellus näyttää toimiessaan. Esimerkki 2.2 GUI-sovellus, joka esittää ToDo -listaa. import java.awt. ; import java.io. ; import java.awt.event. ; import java.util. ; import java.text. ; public class ToDo { private static String tdsto = "ToDo.txt"; public static void main(string[] args) throws Exception { Frame kehys = new Frame("To Do -lista"); kehys.setsize(500, 300); kehys.setlayout(new BorderLayout()); kehys.setbackground(color.gray); Label l = new Label(new SimpleDateFormat().format(new Date()), Label.CENTER); kehys.add(l, BorderLayout.NORTH); File f = new File(tdsto); if (!f.exists()) { kehys.add(new Label("Nothing to do!"), BorderLayout.CENTER); kehys.setvisible(true); return; // Tiedosto olemassa. Luetaan ToDo-lista. BufferedReader fin = new BufferedReader(new InputStreamReader(new FileInputStream(f))); Choice lista = new Choice(); String rivi; while ((rivi = fin.readline()) null) if (!rivi.trim().equals("")) lista.add(rivi.trim()); // Suljetaan tiedosto. fin.close(); kehys.add(lista, BorderLayout.CENTER); kehys.pack(); kehys.setvisible(true);

10 10 LUKU 2. AWT:N PERUSTEET Kuva 2.3: Graafinen ToDo-sovellus (ei appletti). Graafinen ToDo-sovellus on toiminnaltaan hyvin suoraviivainen. Metodin main aluksi luodaan Frame-tyyppinen kehysolio, johon sovelletaan heti määrittelyitä: asetaan koko, sijoittelumanageri ja taustan väri. Kehyksen otsikko on määritelty jo luonnin yhteydessä. Tämän jälkeen luodaan tekstileimaolio, jonka sisällöksi lasketaan käynnistyshetken mukainen päiväys. Kyseinen leima laitetaan osaksi kehystä. Tämän jälkeen avataan vakioniminen (ToDo.txt) tekstitiedosto, josta on tarkoitus lukea kaikki sen rivit. Lukemisen alustustoimenpiteiden jälkeen luodaan pudotusvalikko-olio (Choice). Tähän olioon lisätään vaihtoehtoja while-silmukassa. Tiedostosta luetaan kaikki rivit, mutta pudotusvalikkoon lisätään vain ne rivit, joissa on muitakin merkkejä kuin ns. white space-merkkejä. Lopuksi tiedostovirta suljetaan, pudotusvalikko lisätään kehykseen, kehyksen koko supistetaan minimiin ( pack -metodin kutsu) ja kehyksestä tehdään näkyvä, eli se piirretään ikkunointijärjestelmän toimesta. Sovellukseen voi kohdistaa muutamia toimenpiteitä. Sovelluksen luomaa ikkunaa voi siirtää paikasta toiseen, ikkunan kokoa voi muuttaa ja valikossa olevaa valintaa voi vaihtaa. Päiväystä edustavaa tekstiä ei voi muuttaa. Oikeastaan tämä sovellus ei kuitenkaan reagoi juuri mihinkään tapahtumaan: valinnan vaihtamisella voisi olla jokin seuraus sovelluksen toiminnan kannalta. ToDo-sovellukseen ei liity yhtään itse tehtyä tapahtumankäsittelijää tällä on erityisesti se ikävä seuraus, että ikkunan sulkeminen ei onnistu! Yritys sulkea sovellus kyllä generoi tietynlaisen tapahtuman, mutta mikään tapahtumankäsittelijä ei ota sitä kiinni. Ikkunan sulkeminen onnistuu vain lopettamalla itse sovellus brutaalisti Päivän mietelause Seuraava esimerkki 2.3 näyttää luokan Mietelause. Luokka Mietelause on GUI-sovellus, joka tietynlaisesta mietelauseita sisältävästä tiedostosta arpoo yhden ja esittää sen GUI-sovelluksen ikkunassa, ks. kuva 2.4. Tähän sovellukseen liittyy yksi tapahtumankäsittelijä, joka toteutetaan sisäluokalla IkkunaKuuntelija. Kyseisen luokan rungossa toteutetaan vain yksi metodi, jonka seurauksena ikkuna johon tämän tapahtumankäsittelijän mukainen olio on kiinnitetty suljetaan ja koko sovellus lopetetaan. Tällaisen tapahtumankäsittelijän oleminen osana GUI-sovellusta on hyvin tavallista. Esimerkki 2.3 poikkeaa esimerkistä 2.2 myös sovelluksen pääluokan muodon osalta. Luokka Mietelause perii luokan Frame ja on siten uudenlainen säiliöluokka. Nyt main -metodi on hyvin suppea ja sen tarkoitus on vain luoda kehysolio, johon komponentit sitten sijoitetaan. Koko GUIsovelluksen alustusosa tehdään kehysolion luontimetodissa tästä syystä main -metodissa vain luodaan kehysolio (ottamatta sitä edes talteen mihinkään muuttujaan). Luontimetodissa Mietelause(String 6 Esim. ctrl-c annettuna ikkunalle, josta sovellus käynnistettiin, on monissa ympäristöissä toimiva ratkaisu...

11 2.3. ESIMERKKEJÄ 11 t) tehdään hyvin samanlaisia asioita kuin esimerkin 2.2 main -metodissa. Nyt kuitenkin kehyksen metodeja voidaan kutsua suoraan, koska Mietelause on itse määritelty kehys. Toinen erikoisuus on kehykseen liitettävän ikkunatapahtumien kuuntelijan luominen ( new IkkunaKuuntelija() ) ja liittäminen kehysolioon ( addwindowlistener(... ) ). Luokan Mietelause konstruktori on liian pitkä ja konstruktorin lopussa olevan setvisible(true) :n tulisi olla main-metodissa. Kuva 2.4: Graafinen Mietelause-sovellus. Esimerkki 2.3 GUI-sovellus, joka esittää arpomansa päivän mietelauseen. import java.awt. ; import java.io. ; import java.awt.event. ; import java.util. ; import java.text. ; public class Mietelause extends Frame { private String tdsto; private static String fixed_tdsto = "Mietelauseet.txt"; public static void main(string[] args) throws Exception { if (args.length > 0) new Mietelause(args[0]); else new Mietelause(); public Mietelause() throws Exception { this(fixed_tdsto); jatkuu...

12 12 LUKU 2. AWT:N PERUSTEET Esimerkki 2.3. Mietelause-sovellus (jatkoa). jatkuu... public Mietelause(String t) throws Exception { tdsto = t; setsize(500, 300); settitle("päivän mietelause"); setlayout(new GridLayout(0,1)); setbackground(color.yellow); add(new Label(new SimpleDateFormat(). format(new Date()), Label.CENTER)); addwindowlistener(new IkkunaKuuntelija()); File f = new File(tdsto); if (!f.exists()) { add(new Label("Mietetiedostoa "+tdsto+"ei ole!")); return; // Tiedosto olemassa. Luetaan miete. BufferedReader fin = new BufferedReader(new InputStreamReader(new FileInputStream(f))); int lkm = Integer.parseInt(fin.readLine().trim()); int kohde = (int)(math.random() lkm); String rivi; for (int i=0; i<kohde; i++) while ((rivi = fin.readline()) null) if (rivi.trim().equals("")) break; // Seuraavaksi vuorossa on tulostettava miete. while ((rivi = fin.readline()) null) if (rivi.trim().equals("")) break; else add(new Label(rivi, Label.LEFT)); // Suljetaan tiedosto. fin.close(); pack(); setvisible(true); class IkkunaKuuntelija extends WindowAdapter { public void windowclosing(windowevent e) { // Ikkunaa ollaan sulkemassa. e.getwindow().dispose(); System.exit(0);

13 2.3. ESIMERKKEJÄ Henkilöiden esittelyappletti Tämän kohdan viimeinen esimerkki 2.4 on appletti, johon on liitetty 5 graafista komponenttia. Kaksi komponenteista ovat nappuloita, joihin kumpaankin on liitetty tapahtumankäsittelijäolio. Appletin tehtävänä on lukea tiedostosta henkilöitä koskevia tietoja: kullakin rivillä on pilkulla erotettuna henkilön status, nimi ja sen tiedoston URL, jossa on henkilön kuva 7. Appletin init -metodia kutsutaan appletin lataamisen jälkeen selaimen toimesta, ja sen tehtävänä on luoda tarvittavat graafiset komponentit. Aluksi luodaan edellinen- ja seuraavanappulat, henkilön statusta ja nimeä esittävät leimat ja piirtopinta (itse määritelty luokka HenkilonKuva), jonka avulla esitetään henkilön (gif-)kuva. Edellisiin olioihin sovelletaan määrittelyitä ja ne sijoitetaan appletin säiliöön ( add -metodin kutsuilla). Sitten nappuloihin liitetään tapahtumankäsittelijät ja lopuksi verkon kautta ladataan tiedostossa olevat henkilöiden tiedot ja sitä kautta henkilöiden kuvat. Henkilöiden tiedot muodostavat periaatteessa kaksisuuntaisen listan, ja appletilla on mahdollista kulkea tässä listassa molempiin suuntiin. Aluksi esitettävä nykyinen henkilö on ensimmäinen tiedostossa määritelty henkilö. Lopuksi kutsutaan vielä metodia päivitätiedot, joka päivittää nykyisen henkilön tiedot leimojen ja piirtopinnan arvoiksi. Nappuloihin liitettyjen kuuntelijoiden idea on vain päivittää tietoa, jonka perusteella tiedetään kuka henkilöistä on nykyinen. Tiedon päivittämisen jälkeen kumpikin kuuntelijoista kutsuu metodia päivitätiedot, joka päivittää arvoja appletilla (ja pakottaa muuttuneiden arvojen piirtämisen uudelleen). Huomaa, miten henkilön status- ja nimitietoja esittävät leimat talletetaan Panel-tyyppiseen säiliöön. Metodissa päivitätiedot kyseiset leimaoliot poistetaan säiliöstä ja niiden tilalle laitetaan uutta henkilöä vastaavat uudet 8 leimaoliot. Huomaa myös, miten helppoa kuvan piirtäminen on Canvas:sta johdetulle HenkilonKuva-tyyppiselle piirtopinnalle, kunhan ensin verkosta haettujen (gif-)kuvien perusteella on muodostettu kuvia esittävät Image-tyyppiset oliot. Seuraavassa esimerkki HTML-tiedostosta, jolta esimerkin 2.4 appletti voidaan käynnistää. <html> <title> Henkilökunnan esittely appletilla </title> <body> <APPLET code=staffapplet.class width=410 height=500> <PARAM NAME=Tiedosto VALUE=" </APPLET> </body> </html> 7 Kuvien tulee olla samalla palvelimella, minne appletti sijoitetaan. 8 Itse asiassa vanhoja leimaolioita ei olisi tarvinnut poistaa. Niiden sisältöä olisi ollut mahdollista muuttaa.

14 14 LUKU 2. AWT:N PERUSTEET Esimerkki 2.4 StaffApplet, joka esittelee henkilöitä. import java.awt. ; import java.awt.event. ; import java.applet. ; import java.net. ; import java.util. ; import java.io. ; public class StaffApplet extends Applet { private static final int MAXLKM = 200; private String[] tittelit = new String[MAXLKM]; private String[] nimet = new String[MAXLKM]; private Image[] kuvat = new Image[MAXLKM]; private int lkm, nykyinen; private HenkilonKuva kuva; private Button enappula, snappula; private Label henkilönnimi, henkilönstatus; private Panel henkilö; public void init() { snappula = new Button("Seuraava"); enappula = new Button("Edellinen"); kuva = new HenkilonKuva(); henkilönnimi = new Label("Henkilön nimi", Label.LEFT); henkilönstatus = new Label("Virka",Label.RIGHT); henkilö = new Panel(); henkilö.setlayout(new BorderLayout()); henkilö.add(henkilönnimi, BorderLayout.WEST); henkilö.add(henkilönstatus, BorderLayout.EAST); setlayout(new BorderLayout()); add(henkilö, BorderLayout.NORTH); add(snappula, BorderLayout.EAST); add(enappula, BorderLayout.WEST); add(kuva, BorderLayout.CENTER); // Kuuntelijat snappula.addactionlistener(new SeuraavaKuuntelija()); enappula.addactionlistener(new EdellinenKuuntelija()); // Luetaan tiedot tiedostosta. try { URL osoite = new URL(getParameter("Tiedosto")); URLConnection yhteys = osoite.openconnection(); InputStream tiedostovirta = yhteys.getinputstream(); BufferedReader tiedosto = new BufferedReader(new InputStreamReader(tiedostovirta)); String rivi; int i = -1;

15 2.3. ESIMERKKEJÄ 15 Esimerkki 2.4. StaffApplet (jatkoa). jatkuu... while ((rivi = tiedosto.readline()) null) { i++; StringTokenizer st = new StringTokenizer(rivi, ","); tittelit[i] = st.nexttoken().trim(); nimet[i] = st.nexttoken().trim(); kuvat[i] = getimage(new URL(st.nextToken().trim())); lkm = i+1; nykyinen = 0; tiedosto.close(); tiedostovirta.close(); catch (MalformedURLException e) { showstatus("url:n avaaminen ei onnistunut " + e.tostring()); catch (IOException e) { showstatus("virhe tiedoston käsittelyssä: " + e.tostring()); päivitätiedot(); public void päivitätiedot() { henkilö.remove(henkilönnimi); henkilö.remove(henkilönstatus); henkilönnimi = new Label(nimet[nykyinen], Label.LEFT); henkilönstatus = new Label(tittelit[nykyinen], Label.RIGHT); henkilö.add(henkilönnimi, BorderLayout.WEST); henkilö.add(henkilönstatus, BorderLayout.EAST); henkilö.validate(); kuva.repaint(); class HenkilonKuva extends Canvas { public void paint(graphics g) { if (kuvat[nykyinen] == null) showstatus("henkilöllä "+nykyinen+"ei kuvaa."); else g.drawimage(kuvat[nykyinen], 0, 0, this); jatkuu...

16 16 LUKU 2. AWT:N PERUSTEET Esimerkki 2.4. StaffApplet (jatkoa). jatkuu... class SeuraavaKuuntelija implements ActionListener { public void actionperformed(actionevent e) { nykyinen++; if (nykyinen lkm) nykyinen = lkm-1; päivitätiedot(); class EdellinenKuuntelija implements ActionListener { public void actionperformed(actionevent e) { nykyinen--; if (nykyinen < 0) nykyinen = 0; päivitätiedot();

17 Luku 3 AWT:n peruskomponentteja Tässä luvussa jatketaan AWT:hen tutustumista. AWT:n komponenteista käsitellään ensin kaikkien komponenttien yliluokka Component luvussa 3.1. Vastaavasti kaikkien säiliöiden yliluokka Container käsitellään luvussa 5.1. Näiden kahden luokan tarkoitus on lähinnä määritellä, mitä yleisiä ominaisuuksia on kaikilla graafisilla komponenteilla ja säiliöillä. Komponenttien yleisten ominaisuuksien jälkeen perehdytään suhteellisen yksinkertaisiin komponentteihin: nimikkeet eli leimat (luku 3.2), nappulat eli painikkeet (luku 3.3), piirtokangas (luku 3.4), pudotusvalikko (luku 3.5) ja erilaiset tekstikomponentit (luku 3.6). Näiden komponenttien yhteydessä annetaan myös esimerkkejä niihin liitettävistä tapahtumankäsittelijöistä. 3.1 Komponenttien yliluokka Component Luokka Component on abstrakti yliluokka kaikille graafisille komponenteille. Sen tehtävänä on määritellä komponenttien yhteiset ominaisuuden määrittelemällä joukko metodeja. Vaikka Component onkin abstrakti luokka, niin mikään sen metodeista ei ole abstrakti. Luokka Component toteuttaa seuraavien rajapintojen kaikki metodit: ImageObserver, MenuContainer ja Serializable (ei mitään toteutettavaa). Alle on poimittu joitakin luokan Component n. 150 metodista 1. Metodit voinee ryhmitellä seuraaviin osiin: tapahtumankäsittely, komponettiin liittyvien määrittelyiden asettaminen voimaan (väri, näkyvyys, fontti,... ) ja edellisten havainnointi. Lisäksi on vielä joitakin sekalaisia metodeja (ja runsaasti vanhentuneita metodeja). Kaikkiin komponentteihin voi yleisesti kohdistua useanlaisia tapahtumia. Näitä ovat mm. seuraavat komponettitapahtumat Luokka ComponentEvent. Tapahtuman syynä on komponentin kokoon tai näkyvyyteen kohdistunut muutos. fokusointitapahtumat Luokka FocusEvent; syynä on fokuksen (minne näppäimistön merkit kohdistuvat) saaminen tai menettäminen. näppäintapahtumat Luokka KeyEvent; tapahtuma johtuu näppäimistön merkin painamisesta (tai vapauttamisesta). hiiritapahtumat Luokka MouseEvent; tapahtuman syynä on se, että hiirellä on tehty jotain: siirretty, klikattu, hiiren osoitin on siirtynyt komponentin alueelle,... 1 Lisäksi on n. 50 metodia, joita ei ole enää tarkoitus käyttää (yhteensopivuutta JDK 1.0:n suuntaan). 17

18 18 LUKU 3. AWT:N PERUSKOMPONENTTEJA Komponentteihin voi kohdistua muitakin yleisiä tapahtumia. Tapahtumia ja niiden käsittelyä käsitellään perusteellisesti luvussa 4. void addcomponentlistener(componentlistener l) Lisää komponenttitapahtumien kuuntelijan l komponenttiin. void removecomponentlistener(componentlistener l) Poistaa komponenttitapahtumien kuuntelijan l komponentista. void addfocuslistener(focuslistener l) Lisää fokusointitapahtumien kuuntelijan l komponenttiin. void removefocuslistener(focuslistener l) Poistaa fokusointitapahtumien kuuntelijan l komponentista. void addkeylistener(keylistener l) Lisää näppäintapahtumien kuuntelijan l komponenttiin. void removekeylistener(keylistener l) Poistaa näppäintapahtumien kuuntelijan l komponentista. void addmouselistener(mouselistener l) Lisää hiiritapahtumien kuuntelijan l komponenttiin. void removemouselistener(mouselistener l) Poistaa hiiritapahtumien kuuntelijan l komponentista. void addmousemotionlistener(mousemotionlistener l) Lisää hiiriliiketapahtumien kuuntelijan l komponenttiin. void removemousemotionlistener(mousemotionlistener l) Poistaa hiiriliiketapahtumien kuuntelijan l komponentista. protected void disableevents(long mask) Estää mask:n määräämien tapahtumien lähettämisen komponentille. protected void enableevents(long mask) Sallii mask:n mukaisten tapahtumien lähettämisen komponentille. void dispatchevent(awtevent e) Lähettää tapahtuman e komponentille. protected void processevent(awtevent e) Prosessoi komponenttiin kohdistuvan tapahtuman e. void add(popupmenu p) Lisää ponnahdusmenun p komponenttiin. boolean contains(int x, int y) Kuuluuko piste (x,y) komponentin graafiseen alueeseen. Color getbackground() Palauttaa komponentin taustavärin. Color getforeground() Palauttaa komponentin päävärin. Cursor getcursor() Palauttaa tähän komponenttiin liitetyn kursorin (kun hiirikohdistin on tämän komponentin päällä, sillä on Cursor-luokan olion määräämä graafinen ulkoasu). Font getfont() Palauttaa komponenttiin liitetyn fontin. Graphics getgraphics() Palauttaa komponentin graafisen ulkoasun. int getheight()

19 3.1. KOMPONENTTIEN YLILUOKKA COMPONENT 19 Palauttaa komponentin korkeuden. int getwidth() Palauttaa komponentin leveyden. Point getlocation() Palauttaa komponentin yläkulman koordinaatit. Dimension getsize() Palauttaa komponentin koon. Dimension getmaximumsize() Palauttaa komponentin maksimikoon. Dimension getminimumsize() Palauttaa komponentin minimikoon. Dimension getpreferredsize() Palauttaa komponentin suositeltavan koon. Container getparent() Palauttaa sen säiliön, johon komponentti kuuluu. boolean hasfocus() Onko komponentilla (näppäimistö)fokus? boolean isshowing() Näkyykö komponentti? boolean isenabled() Voidaanko komponenttiin kohdistaa käyttäjän toimenpiteitä? void setbackground(color c) Asettaa c:n komponentin taustaväriksi. void setforeground(color c) Asettaa c:n komponentin pääväriksi. void setfont(font f) Asettaa komponentin tekstifontiksi f:n. void setsize(dimension d) Asettaa komponentin uudeksi kooksi d:n. void setbounds(int x, int y, int dx, int dy) Asettaa komponentin uudeksi kooksi dx dy:n ja sijoittaa komponentin kohtaan (x, y). void paint(graphics g) Piirtää komponentin piirtoalueelle g. void repaint() Piirtää komponentin uudelleen. void requestfocus() Vaatii (näppäimistö)fokusta komponentille. void setcursor(cursor c) Asettaa komponentin uudeksi kursorikuvioksi c:n. void setenabled(boolean b) Jos b = true, komponenttiin voidaan kohdistaa käyttäjän toimenpiteitä. false: ei voida (komponentti on harmaa ). void setvisible(boolean b) Jos b = true, komponentti on näkyvissä; false: piilossa (mutta silti olemassa). void validate() Varmistaa, että komponentin ulkoasu on vastaa sen nykyistä tietosisältöä. Asetusten pitäisi tulla heti voimaan muutenkin, käytetään lähinnä luokan Container yhteydessä.

20 20 LUKU 3. AWT:N PERUSKOMPONENTTEJA 3.2 Nimike Label Nimike eli tekstileima on graafinen komponentti Label, joka esittää määritellyn sisältöistä tekstiä määritellyllä fontilla (ja koolla). Tekstiä ei pääse muuttamaan käyttöliittymässä editoimalla siihen jonkin uuden sisällön, mutta tekstiä voi kyllä muuttaa ohjelman toimesta, kutsumalla tekstileimaolion metodia settext. Leimaolioon liittyy myös tieto tekstin kohdistuksesta, jonka mahdollisia arvoja ovat Label.LEFT (vasempaan reunaan), Label.RIGHT (oikeaan reunaan) ja Label.CENTER (oikeaan reunaan). Oletuksena on vasen reuna. Jonkin muun kohdistuskoodin (kokonaisluku) antaminen nostaa poikkeuksen (IllegalArgumentException). Luokan Label tapa koodata luokkavakioiden avulla välitettäviä laillisia parametriarvoja edustaa yleisesti sovellettua tapaa AWT:ssä. Luokan Label metodeja esitellään taulukossa 3.1. Näiden lisäksi ovat luonnollisesti luokasta Component perityt metodit. Label() Konstruktori, joka alustaa sisällön arvoksi tyhjän tekstin. Label(String t) Konstruktori, joka alustaa sisällöksi t:n. Label(String t, int align) Konstruktori, joka alustaa sisällöksi t:n ja align määrittää tekstin kohdistuksen. String gettext() Funktio, joka palauttaa leiman sisältämän merkkijonon. void settext(string t) Asettaa leiman uudeksi sisällöksi t:n. int getalignment() Palauttaa tekstin kohdistuksen ilmaisevan arvon. void setalignment(int align) Asettaa kohdistuksen uudeksi arvoksi align:n. Taulukko 3.1: Luokan Label konstruktoreita ja metodeja. Esimerkki 3.1 havainnollistaa leimoja ja niiden metodeja. Sovelluksessa luodaan kolme leimaa käyttäen eri konstruktoreita. Ks. kuva 3.1. Leimoihin liitetään erilaiset fontit ja erilainen sisältö. Ne lisätään kehykseen ja kuhunkin leimaan liitetään kuuntelija, joka syklisesti kierrättää kohdistuksen arvoa, kun leimaa klikataan. Kehykseen liitetään tavanomainen ikkunan sulkeva tapahtumankäsittelijä. Kuva 3.1: Graafinen LeimaTesti-sovellus.

21 3.2. NIMIKE LABEL 21 Esimerkki 3.1 GUI-sovellus, joka demonstroi leimoja. import java.awt. ; import java.awt.event. ; // Luo joukon leimaolioita ja laittaa niihin klikkaukseen // reagoivan kuuntelijan kuhunkin. public class LeimaTesti { public static void main(string[] args) { Font ss18 = new Font("SansSerif", Font.PLAIN, 18); Font tr14 = new Font("TimesRoman", Font.PLAIN, 14); Font trb14 = new Font("TimesRoman", Font.BOLD, 14); Label l1 = new Label("Ensimmäinen teksti", Label.LEFT); Label l2 = new Label("Toinen teksti"); l2.setalignment(label.center); Label l3 = new Label(); l3.setalignment(label.right); l3.settext("kolmas teksti"); Frame f = new Frame("Leimojen testaus"); f.setlayout(new GridLayout(0,1)); f.add(l1); f.add(l2); f.add(l3); l1.setfont(ss18); l2.setfont(tr14); l3.setfont(trb14); LeimanKuuntelija lk = new LeimanKuuntelija(); l1.addmouselistener(lk); l2.addmouselistener(lk); l3.addmouselistener(lk); f.addwindowlistener(new IkkunanSulkija()); f.pack(); f.setvisible(true); // LeimaTesti class LeimanKuuntelija extends MouseAdapter { public void mouseclicked(mouseevent e) { // Tiedetään, että kohde on jokin leimoista. Label l = (Label)e.getSource(); int uusi = 0; switch (l.getalignment()) { case Label.LEFT: uusi = Label.CENTER; break; case Label.CENTER: uusi = Label.RIGHT; break; case Label.RIGHT: uusi = Label.LEFT; break; l.setalignment(uusi); // LeimanKuuntelija

22 22 LUKU 3. AWT:N PERUSKOMPONENTTEJA 3.3 Painike Button Painike eli nappula on hieman kuten tekstileima: nappulan sisältönä on tekstiä. Nappulan tekstin voi vaihtaa toiseksi, mutta leiman tapaan, nappulan tekstiin ei liity mahdollisuutta kohdistaa se jompaan kumpaan reunaan. Nappulan ehkä tärkein ominaisuus on ActionEvent-tyyppisen tapahtuman luomisen mahdollisuus. Kun nappulaa klikataan hiirellä, se tuottaa ActionEvent-tyyppisen tapahtuman. Nappulaan on mahdollista liittää Component-luokassa määriteltyjen tapahtumankäsittelijöiden lisäksi myös ActionListener-tyyppisiä käsittelijöitä, jotka osaavat käsitellä ActionEvent-tapahtumia. Näistä tapahtumista ja käsittelijöistä on lisää luvussa 4. Button() Konstruktori, joka alustaa nappulan tekstisisällön arvoksi tyhjän tekstin. Button(String t) Konkstruktori, joka alustaa tekstisisällöksi t:n. String getlabel() Funktio, joka palauttaa nappulan tekstisisällön. void setlabel(string t) Asettaa nappulan tekstisisällöksi t:n. void addactionlistener(actionlistener l) Liittää nappulaan ActionListener-tyyppisen tapahtumankäsittelijän l. void removeactionlistener(actionlistener l) Poistaa nappulaan liitetyn tapahtumankäsittelijän l. void setactioncommand(string c) Kun ActionEvent laukeaa, siihen liittyy tieto komennon nimestä. Tällä metodilla nimen voi asettaa. Oletusarvo on nappulan nimi. String getactioncommand() Palauttaa komennon nimen. Taulukko 3.2: Luokan Button konstruktoreita ja metodeja. Kuva 3.2: Graafinen NappulaTesti-sovellus. Esimerkissä 3.2 luodaan 5 nappulaa, joilla kullakin on oma tekstisisältö. Kaikki nappulat lisätään luotuun kehykseen ja kuhunkin liitetään kuuntelija, joka kuuntelee ActionEvent:jä. Neljä ensimmäistä nappulaa operoivat viidennen nappulan tilalla: kaksi ensimmäistä poistavat tai mahdollistavat viidennen klikkaamisen. Toiset kaksi piiloittavat tai tuovat näkyviin nappulan. Kun viides nappula on klikattavissa ja sitä klikataan, kasvaa tekstikentässä olevan laskurin arvo. Esto-nappulaan on liitetty myös hiiren klikkauksia kuunteleva käsittelijä, joka klikkauksen seurauksena vähentää laskurin arvoa yhdellä. Tuolloin nappulaan liittyy kaksi erilaista tapahtumankäsittelijää, jotka kummatkin suoritetaan nappulaa klikattaessa.

23 3.3. PAINIKE BUTTON 23 Esimerkki 3.2 GUI-sovellus, joka demonstroi nappuloita. import java.awt. ; import java.awt.event. ; public class NappulaTesti { private static Button b1, b2, b3, b4, b5; private static int laskuri = 0; private static TextField lt = new TextField(20); public static void main(string[] args) { Frame f = new Frame("Nappulatesti"); f.setlayout(new GridLayout(0,2)); b1 = new Button("Mahdollista"); b2 = new Button("Estä"); b3 = new Button("Piilota"); b4 = new Button("Esiin"); b5 = new Button("Lisää laskuria"); f.add(b1); f.add(b2); f.add(b3); f.add(b4); f.add(b5); f.add(lt); NappulaKuuntelija nk = new NappulaKuuntelija(); b1.addactionlistener(nk); b2.addactionlistener(nk); b3.addactionlistener(nk); b4.addactionlistener(nk); b5.addactionlistener(nk); b2.addmouselistener(new B2Kuuntelija()); f.addwindowlistener(new IkkunanSulkija()); f.pack(); f.setvisible(true); piirrälaskuri(); // main private static void piirrälaskuri() { lt.settext("laskuri: " + laskuri); static class NappulaKuuntelija implements ActionListener { public void actionperformed(actionevent e) { Button b = (Button)e.getSource(); if (b==b1) { b5.setenabled(true); else if (b==b2) { b5.setenabled(false); else if (b==b3) { b5.setvisible(false); else if (b==b4) { b5.setvisible(true); else if (b==b5) { laskuri++; piirrälaskuri(); // NappulaKuuntelija static class B2Kuuntelija extends MouseAdapter { public void mouseclicked(mouseevent e) { laskuri--; piirrälaskuri(); // B2Kuuntelija // NappulaTesti

24 24 LUKU 3. AWT:N PERUSKOMPONENTTEJA 3.4 Piirtokangas Canvas Piirtokangas, luokka Canvas, on vain tyhjä alue, jolle voidaan piirtää ja/tai sijoittaa kuva. Aiemmin esimerkissä 2.4 luotiin uusi luokka HenkilonKuva perimällä luokasta Canvas. Tämä on tyypillinen tapa käyttää piirtokangasta: määritellään perinnän yhteydessä metodi paint uudelleen. Luokan Canvas määrittelemiä metodeja on esitetty taulukkossa 3.3 metodeja on hyvin vähän, piirtokangas perii lähes kaikki ominaisuutensa suoraan luokalta Component. Canvas() Konstruktori, joka luo tyhjän piirtokankaan. Kankaan koon voi asettaa Component:sta perityillä metodeilla. void addnotify() Luo vastinolion. void paint(graphics g) Piirtää kankaan g:hen. Kutsutaan lähinnä repaint :n toimesta. Taulukko 3.3: Luokan Canvas konstruktoreita ja metodeja. 3.5 Pudotusvalikko Choice Pudotusvalikko on eräänlainen valintalista. Listassa voi olla useita tekstialkioita, joista käyttäjä voi valita yhden. Ainoastaan valittu tekstialkio on näkyvissä. Painaessaan pudotusvalikkoa (tai sen reunassa olevaa nuolta ), pudotusvalikko avautuu ja klikkaamalla voidaan jokin alkioista valita. Valinnan suorittamisen jälkeen pudotusvalikko sulkeutuu automaattisesti. Jos pudotusvalikon valintalista on niin pitkä, ettei se mahdu näytölle, tulee pudotusvalikon reunalle automaattisesti liu utin. Nappulan tavoin pudotusvalikkoon liittyy erityinen tapahtuma ItemEvent ja mahdollisesti sen mukaisia tapahtumankäsittelijöitä (itse tehtyinä ja komponenttiin ohjelmassa liitettyjä), joiden yliluokkana toimii ItemListener. Tapahtumia voi periaatteessa olla kahdenlaisia: jokin tekstialkio tulee valituksi tai valinta tulee poistetuksi. Luokan ItemListener perijöiden tarvitsee toteuttaa vain yksi metodi: void itemstatechanged(itemevent e). Luokalla ItemEvent on metodi getitemselectable(), jolla saadaan selville tapahtuman kohdeolio. Metodi getitem() kertoo valitun alkion. Metodilla getstatechange() saadaan selville onko kysymys valinnasta (vakio ItemEvent.SELECTED) vai valinnan poistosta (vakio ItemEvent.DESELECTED). Ilmeisesti pudotusvalikko generoi vain valintatapahtumia. Taulukkoon 3.4 on kerätty pudotusvalikon hyödyllisiä metodeja. Esimerkissä 3.3 luodaan vakiomuotoinen pudotuslista, ja lisätään se sekä yksi leima ja kaksi tekstikenttää appletille. Pudotuslistaan kiinnitetään tapahtumankäsittelijä, joka raportoi valinnan suorittamisja poistamistapahtumista. Ks. kuva 3.3.

25 3.5. PUDOTUSVALIKKO CHOICE 25 Choice() Konstruktori, joka alustaa sisällöksi tyhjän valintalistan. int getitemcount() Palauttaa valintalistassa olevien alkioiden lukumäärän. String getitem(int i) Palauttaa valintalistan i:nnen alkion. void add(string item) Lisää item:n valintalistan loppuun. Jos item == null, nostetaan NullPointerException. void insert(string item, int i) Lisää (korvaamatta) item:n kohtaan i. Poikkeus IllegalArgumentException, jos i < 0. void remove(string item) Poistaa valintalistasta ensimmäisen item:n esiintymän. void remove(int i) Poistaa valintalistan alkion positiosta i. void removeall() Tyhjentää valintalistan. String getselecteditem() Palauttaa valitun alkion valintalistasta. Palauttaa null, jos ei mitään valittuna. int getselectedindex() Palauttaa valitun alkion indeksinumeron (-1, jos ei mitään valittu). void select(string str) Valitsee listasta ensimmäisen alkion, jonka sisältö sama kuin str. void additemlistener(itemlistener l) Lisää pudotusvalikkoon ItemListener-käsittelijän l. void removeitemlistener(itemlistener l) Poistaa ItemListener-käsittelijän l. Taulukko 3.4: Luokan Choice konstruktoreita ja metodeja. Kuva 3.3: ChoiceTesti-appletti.

26 26 LUKU 3. AWT:N PERUSKOMPONENTTEJA Esimerkki 3.3 Appletti, joka demonstroi pudotusvalikkoja. import java.awt. ; import java.awt.event. ; import java.applet. ; public class ChoiceTesti extends Applet{ private TextField tf_in = new TextField(20); private TextField tf_out = new TextField(20); public void init() { setlayout(new GridLayout(0,1)); add(new Label("Valitse jokin alkio valikosta.")); Choice c = new Choice(); c.add("suomen markka"); c.add("ruotsin kruunu"); c.add("norjan kruunu"); c.add("saksan markka"); c.add("venäjän rupla"); c.add("ranskan frangi"); c.additemlistener(new ListaKuuntelija()); add(c); add(tf_in); add(tf_out); // init class ListaKuuntelija implements ItemListener { public void itemstatechanged(itemevent e) { String kohde = (String)e.getItem(); if (e.getstatechange() == ItemEvent.SELECTED) { tf_in.settext("valittu: " + kohde); else { tf_out.settext("poistettu: " + kohde); // itemstatechanged // ListaKuuntelija // ChoiceTesti 3.6 Tekstikomponentit TextComponent ja TextField sekä TextArea Seuraavaksi käsitellään tekstikomponentit, joiden sisältöä voi mm. editoida (lisätä ja tuhota haluttuja merkkejä). Tekstikomponentin sisällöstä voi myös valita haluamansa osan. Tekstikomponentteja on kahdenlaisia: yksi- (TextField) ja monirivisiä (TextArea). Kummankin yliluokkana toimii yhteiset ominaisuudet määrittävä TextComponent-luokka 2. Valinnan ja sisällön editoinnin lisäksi tekstikomponenttien yhteiset ominaisuudet käsittelevät sisällön ohjelmallista käsittelyä, valitun tekstin tutkimista ja valintaa, tekstisisällön editoitavuuden määrittelyä (kun sisältö ei ole editoitavissa, se muuttuu harmaaksi ), kohdistimen position määrittelemistä ja erityisen TextListener-tyyppisen tapahtuman- 2 Vaikka tämä luokka on konkretti, virallisesti sillä ei ole konstruktoria (JDK:n dokumentaatio). Toisaalta, ainakin JDK 1.1:ssä luokassa esiintyy yksi konstruktori.

27 3.6. TEKSTIKOMPONENTIT TEXTCOMPONENT JA TEXTFIELD SEKÄ TEXTAREA 27 käsittelijän liittämistä ja poistamista. Tapahtumankäsittelijä käsittelee TextEvent-tyyppisen tapahtuman kyseinen tapahtuma syntyy, kun tekstin sisältö muuttuu. Huomaa, että fontin asettamista ei määritellä tässä luokassa, vaan se peritään luokasta Component. Taulukossa 3.5 on lueteltu joitakin hyödyllisiä metodeja. void settext(string t) Asettaa sisällöksi merkkijonon t. String gettext() Palauttaa tekstikomponentin sisältämän tekstin. String getselectedtext() Palauttaa valitun tekstin. int getselectionstart() Palauttaa valitun tekstin alkuposition. int getselectionend() Palauttaa valitun tekstin loppuposition. boolean iseditable() Palauttaa tiedon, voiko sisältöä editoida. void seteditable(boolean b) Asettaa tekstin editoitavuuden. Editoitavissa, jos b == true (oletus), muutoin ei. void select(int s, int e) Valitsee tekstin positiosta s positioon e. void selectall() Valitsee koko tekstikomponentin sisällön. void setcaretposition(int p) Asettaa kohdistimen position. Kirjoitettavat merkit lisätään tähän kohtaan. Tuhoaminen samoin. Poikkeus IllegalArgumentException, jos laiton positio. int getcaretposition() Palauttaa kohdistimen position. void addtextlistener(textlistener l) Lisää TextListener-tyyppisen tapahtumankuuntelijan l. void removetextlistener(textlistener l) Poistaa tapahtumankäsittelijän l. Taulukko 3.5: Luokan TextComponent metodeja Luokka TextField Luokka TextField perii kaikki luokan TextComponent (kuten myös luokan Component) metodit. Luokan TextField oliot edustavat yksirivistä editoitavissa olevaa tekstiä. Komponentilla on koko, joka voidaan asettaa konstruktorien yhteydessä tai erikseen metodilla setcolumns. Tämä koko on vain teksti-ikkunan koko, se ei ole raja syötettävien merkkien lukumäärälle. Kokoa voidaan helposti muuttaa (esim. muuttamalla ikkunan kokoa). Tähän tekstikomponenttiin liittyy myös mahdollisuus määrittää erityinen kaiutusmerkki, jotka käytetään estämään käyttäjän kirjoittamien merkkien näkyminen. Kirjoitettujen merkkien tilalla näkyy kyseinen kaiutusmerkki itse tekstikenttään tallettuvat käyttäjän kirjoittamat merkit. Tällainen toiminnallisuus on tarpeellinen mm. salasanoja kirjoitettaessa. Lisäksi TextField-tyyppisiin komponentteihin voi liittää ActionListener-tyyppisen tapahtumankäsittelijän (tai useita niitä). Tällaiset käsittelijät kuuntelevat ActionEvent-tyyppisiä tapahtumia: sellainen

28 28 LUKU 3. AWT:N PERUSKOMPONENTTEJA kohdistuu tekstikomponenttiin, kun käyttäjä painaa enter:ä. Taulukossa 3.6 on joitakin hyödyllisiä metodeja Luokalla TextField on myös useita muita metodeja tekstikentän fyysisen koon määrittämiseksi. TextField() Konstruktori, joka alustaa sisällön arvoksi tyhjän tekstin. TextField(String t) Konstruktori, joka alustaa sisällöksi t:n. TextField(int cols) Konstuktori, joka alustaa sisällön tyhjäksi ja asettaa tekstikentän kooksi cols. TextField(String t, int cols) Konstruktori, joka alustaa sisällöksi t:n ja kooksi cols. int getcolumns() Palauttaa tekstikentän koon. void setcolumns(int cols) Asettaa tekstikentän koon cols:ksi. Poikkeus IllegalArgumentException, jos cols < 0. char getechochar() Palauttaa erityisen kaiutusmerkin. void setechochar(char c) Asettaa c:n erityiseksi kaiutusmerkiksi. Normaalitilaan palataan, kun c:llä arvo 0. boolean echocharisset() Käytetäänkö erityistä kaiutusmerkkiä kirjoitettavien merkkien tilalla. void addactionlistener(actionlistener l) Liittää komponenttiin ActionListener-tyyppisen tapahtumankäsittelijän l. void removeactionlistener(actionlistener l) Poistaa komponenttiin liitetyn tapahtumankäsittelijän l. Taulukko 3.6: Luokan TextField konstruktoreita ja metodeja. Kuva 3.4: OsoiteTaltioija-sovellus, jolla voi taltioida osoitetietoja tiedostoon. Esimerkissä 3.4 toteutetaan yksinkertainen sovellus, jonka avulla voi taltioida osoitetietoja (tekstimuodossa) tiedostoon. Sovellus esittää lomaketta, jonka tekstikentät käyttäjä voi täyttää. Kehykseen liitettyjen kahden nappulan avulla käyttäjä voi lisätä syötetyt tiedot tiedostoon ja toisaalta tyhjentää lomakkeen. Alhaalla oleva statusrivi kertoo toimenpiteen. Operoitavan tiedoston nimen voi antaa sovellukselle komentoriviparametrina. Ks. kuva 3.4.

29 3.6. TEKSTIKOMPONENTIT TEXTCOMPONENT JA TEXTFIELD SEKÄ TEXTAREA 29 Esimerkki 3.4 OsoiteTaltioija-sovellus, joka demonstroi yksirivistä tekstikenttää. import java.awt. ; import java.awt.event. ; import java.io. ; public class OsoiteTaltioija { private static String tdsto = "osoitteet.txt"; private static TextField t1, t2, t3, t4; private static BufferedWriter fout; private static Label statusleima; public static void main(string[] args) throws Exception { if (args.length == 1) tdsto = args[0]; fout = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(tdsto)))); Frame f = new Frame("Osoitetietojen taltioija"); f.setbackground(color.green); f.setsize(500,500); f.setlayout(new GridLayout(6,2)); Label l1 = new Label("Nimi", Label.RIGHT); Label l2 = new Label("Katuosoite", Label.RIGHT); Label l3 = new Label("Postinumero", Label.RIGHT); Label l4 = new Label("Paikkakunta", Label.RIGHT); t1 = new TextField(30); t2 = new TextField(30); t3 = new TextField(5); t4 = new TextField(20); statusleima = new Label("Status"); f.add(l1); f.add(t1); f.add(l2); f.add(t2); f.add(l3); f.add(t3); f.add(l4); f.add(t4); Button b1 = new Button("Talleta"); Button b2 = new Button("Tyhjennä"); f.add(b1); f.add(b2); f.add(statusleima); b1.addactionlistener(new TalletusKuuntelija()); b2.addactionlistener(new TyhjennysKuuntelija()); f.addwindowlistener(new LopetusKuuntelija()); f.pack(); f.setvisible(true); // main static class TyhjennysKuuntelija implements ActionListener { public void actionperformed(actionevent e) { t1.settext(""); t2.settext(""); t3.settext(""); t4.settext(""); statusleima.settext("tyhjennetty"); // TyhjennysKuuntelija jatkuu...

30 30 LUKU 3. AWT:N PERUSKOMPONENTTEJA Esimerkki 3.4. OsoiteTaltioija-sovellus (jatkoa). jatkuu... static class TalletusKuuntelija implements ActionListener { public void actionperformed(actionevent e) { try { fout.write(t1.gettext() + ";" + t2.gettext() + ";" + t3.gettext() + ";" + t4.gettext()); fout.newline(); fout.flush(); statusleima.settext("talletettu"); catch (IOException e1) { statusleima.settext("ongelmia!"); // TalletusKuuntelija static class LopetusKuuntelija extends WindowAdapter { public void windowclosing(windowevent e) { // Ikkunaa ollaan sulkemassa. try { fout.close(); catch (IOException e1) { e.getwindow().dispose(); System.exit(0); // LopetusKuuntelija // OsoiteTaltioija Luokka TextArea Tämän kohdan lopuksi käsitellään vielä luokkaa TextArea, jonka avulla esitetään monirivisiä tekstikomponentteja. Tähän luokkaan ei TextField:n tapaa liity mahdollisuutta käyttää kaiutinmerkkejä peittämään kirjoitettavat merkit. Myöskään enter:n painaminen ei TextField:n tapaan laukaise erityistä tapahtumaa. Taulukossa 3.7 olevien metodien luettelo (ei täydellinen) ei ole kovin yllättävä TextArea perii suuren osan keskeisestä toiminnallisuudestaan TextComponent:lta ja epäsuorasti Component:lta. Lopuksi esimerkissä 3.5 havainnollistetaan erilaisten tekstikomponenttien toimintaa. Kehykseen luodaan kaksi nappulaa, kaksi yksirivistä tekstikomponenttia ja tekstialue keskelle. Alla olevaan tekstikenttään sovelletaan kirjoitetun tekstin normaalin kaiuttumisen korvaamista merkillä *. Nappuloiden tehtävänä on kopioida salatun kentän sisältö joko tekstialueen alkuun tai loppuun. Ylinnä oleva tekstikenttä on sellainen, että sen sisältöä ei voi editoida. Jos tekstialueelta on maalattu hiirellä jokin osa, niin nappuloiden painamisen yhteydessä tämä valittu teksti kopioidaan ylhäällä olevan tekstikentän uudeksi sisällöksi. Ks. kuva 3.5. Huomaa, miten tapahtumankäsittelijän luontioperaatiolle on mahdollista antaa parametri, jonka perusteella luodaan toiminnaltaan hieman erilaisia tapahtumankäsittelijöitä: parametrin arvo määrää, minne tapahtumankäsittelijä kopioi salatun tekstikentän sisällön.

31 3.6. TEKSTIKOMPONENTIT TEXTCOMPONENT JA TEXTFIELD SEKÄ TEXTAREA 31 TextArea() Konstruktori, joka alustaa sisällön arvoksi tyhjän tekstin. TextArea(String t) Konstruktori, joka alustaa sisällöksi t:n. TextArea(int r, int c) Konstruktori. Tyhjä tekstisisältö. Rivejä r ja sarakkeita c. TextArea(String t, int r, int c) Konstruktori. Tekstisisältö t, rivejä r ja sarakkeita c. TextArea(String t, int r, int c, int s) Konstruktori. Kuten edellinen, mutta s määrittelee liu uttimien näkyvyyden. Useita arvoja, esim. TextArea.SCROLLBARS_BOTH: molemmat liu uttimet käytössä. void insert(string str, int pos) Asettaa tekstin str positioon pos (lisää, ei korvaa). void append(string str) Lisää tekstin str loppuun. int getrows() Palauttaa rivien lukumäärän. void setrows(int rows) Asettaa rivien lukumäärän rows:ksi. int getcolumns() Palauttaa sarakkeiden koon. void setcolumns(int cols) Asettaa sarakkeiden koon cols:ksi. int getscrollbarvisibility() Palauttaa tiedon liu uttimien näkyvyydestä. Taulukko 3.7: Luokan TextArea konstruktoreita ja metodeja. Kuva 3.5: TextTesti-sovellus, joka havainnollistaa erilaisia tekstikenttiä.

32 32 LUKU 3. AWT:N PERUSKOMPONENTTEJA Esimerkki 3.5 TextTesti-sovellus, joka demonstroi erilaisia tekstikenttiä. import java.awt. ; import java.awt.event. ; public class TextTesti extends Frame { private TextField valittu; private TextArea alue; private TextField salattu; public static void main(string[] args) { new TextTesti(); // main public TextTesti() { super("testailua tekstikomponenteilla"); setbackground(color.gray); setlayout(new BorderLayout()); Button b1 = new Button("Liitä alkuun"); Button b2 = new Button("Liitä loppuun"); add(b1, BorderLayout.EAST); add(b2, BorderLayout.WEST); valittu = new TextField(30); valittu.seteditable(false); // ei saa editoida alue = new TextArea("tekstiä", 4, 30); salattu = new TextField(30); salattu.setechochar( ); b1.addactionlistener(new TekstinSiirtaja(true)); b2.addactionlistener(new TekstinSiirtaja(false)); add(valittu, BorderLayout.NORTH); add(alue, BorderLayout.CENTER); add(salattu, BorderLayout.SOUTH); addwindowlistener(new IkkunanSulkija()); pack(); setvisible(true); // TextTesti class TekstinSiirtaja implements ActionListener { private boolean alkuunko; TekstinSiirtaja(boolean b) { alkuunko = b; public void actionperformed(actionevent e) { String t = salattu.gettext(); valittu.settext(alue.getselectedtext()); if (alkuunko) alue.insert(t,0); else alue.append(t); //TekstinSiirtaja // TextTesti

33 Luku 4 Tapahtumat ja tapahtumankäsittely Tapahtumat (events) ovat Javassa olioita. Niitä ei kuitenkaan tyypillisesti ohjelmassa luoda itse, vaan GUI-ohjelman suorituksen aikana käyttäjän aiheuttamat toimenpiteet, kuten hiirellä klikkaus, näppäimen painallus tai esimerkiksi hiiren siirtäminen, aiheuttavat tapahtumaolion luonnin käyttöympäristön toimesta. Tällainen tapahtumaolio välitetään käyttöympäristön ja Javan suoritusaikaisen koneiston toimesta tapahtumankäsittelijälle (tai useille sellaisille; event handler). Tapahtumankäsittelijä on myös olio. Niitä esittävien luokkien määrityksiä kirjoitetaan graafiseen käyttöliittymään tyypillisesti useita. Itse asiassa, juuri tapahtumankäsittelijöiden avulla toteutetaan GUI-sovelluksen toiminnallisuus. Kuhunkin AWT:n komponenttiin voi liittyä useanlaisia tapahtumankäsittelijöitä. Lisäksi, kunkin tyyppisiä tapahtumankäsittelijöitä voi olla useita niitä vastaava tapahtuma laukaisee (fire) niiden kaikkien suorittamisen. On vieläpä niin, että yksittäinen käyttäjän toimenpide voi laukaista usean tyyppisiä tapahtumankäsittelijöitä. AWT:n tapahtumiin liittyvät luokat ovat pääasiallisesti paketissa java.awt.event. Tapahtumia esittävien luokkien nimet ovat muotoa XXXEvent, esim. MouseEvent ja ActionEvent. Vastaavasti tapahtumankäsittelijöiden muodon kuvaavien rajapintojen nimi on muotoa XXXListener, esim. MouseListener ja ActionListener. Jos rajapinnassa määritellään useita metodeja, AWT:ssä on määritelty ns. adapteriluokka (adapter class), jonka nimi on XXXAdapter, esim. MouseAdapter. Adapteriluokat toteuttavat kaikki vastaavan rajapinnan metodit kunkin metodin runko on tyhjä. Adapteriluokkien tarkoitus on helpottaa tapahtumankäsittelijöiden tekemistä, sillä tyypillisesti rajapinnan metodeista halutaan toteuttaa vain yksi (ja jättää muiden toteutus tyhjäksi). Seuraavassa käsitellään ensin tapahtumankäsittelyn yleistä mallia kohdassa 4.1. Luvussa 4.2 käsitellään AWT:n tapahtumat. Tapahtumankäsittelijöitä ja niiden muodostamista tutkitaan kohdassa 4.3, ja luvussa 4.4 puolestaan kerrotaan, miten tapahtumankäsittely oikein on toteutettu Javassa. Lopuksi luvussa 4.5 esitetään muutamia esimerkkejä. 4.1 Tapahtumankäsittelyn yleinen malli Javan versiosta 1.1 eteenpäin tapahtumankäsittely on koostunut (1) JVM:ään liittyvästä yleisestä tapahtumien tuottamis- ja kuljetusmekanismista, (2) komponentteihin liitetystä käsittelymekanismista, (3) tapahtumista ja (4) komponentteihin liitettävistä yksittäisistä tapahtumankäsittelijöistä. Versiossa 1.0 tapahtumankäsittely tehtiin hieman eri tavalla vanhan käsittelytavan jäljet ovat vielä nähtävissä useiden deprecated -metodien muodossa. Jos GUI-komponentti tukee esimerkiksi KeyEventtapahtumia, sillä on metodit addkeylistener ja removekeylistener tapahtumankäsittelyolioiden kiinnittämiseksi komponenttiin ja vastaavasti poistamiseksi. Yleisesti, jos komponentti tukee tapahtu- 33

34 34 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY maa XXXEvent, on sillä metodit addxxxlistener(xxxlistener) ja removexxxlistener(xxxlistener). Ainoan poikkeuksen muodostavat hiiritapahtumat (MouseEvent), joita käsitellään kolmenlaisilla käsittelijöillä (ks. taulukko 4.1). Periaatteessa kiinnittämisensä jälkeen tapahtumankäsittelijä kuuntelee komponenttiin kohdistuvia tapahtumia reagoiden niihin tarvittaessa. Reagointi tarkoittaa jonkin tietyn tapahtumankäsittelijän metodin suorittamista. Käytännössä tapahtumankäsittelijät ovat passiivisia olioita, joihin liitetty toiminnallisuus suoritetaan yleisen tapahtumankuljetus ja -käsittelymekanismin toimesta. Tapahtumankäsittelijä on olio, joka on luotu itse tehdystä luokasta. Esimerkiksi, rajapinnasta ActionListener voidaan perimällä muodostaa luokka NappulaKuuntelija, kuten esimerkissä 3.2. Perinnän yhteydessä tapahtumankäsittelijän tarpeellisille metodeille annetaan toteutus tai perittäessä adapteriluokasta tarpeelliset metodit määritellään uudelleen. Kun tapahtuma toimitetaan komponentille, komponentteihin liitetty mekanismi osaa kutsua kunkin tapahtuman yhteydessä sen käsittelystä vastaavaa metodia. Esimerkiksi ActionEvent-tapahtuman yhteydessä tämä kutsuttava metodi on ActionListener-tyyppisen käsittelijän metodi actionperformed(actionevent), joka on siis itse tehdyssä tapahtumankäsittelijässä määritelty perinnän yhteydessä. Kunkin tapahtuman yhteydessä kutsutaan vain yhtä metodia ja sen nimi vaihtelee tapahtumasta (ja tapahtumankäsittelijästä) riippuen. Ks. taulukko 4.1. Tapahtumat ovat olioita, jotka käyttäjän toimenpiteen ( ärsykkeen ) seurauksena syntyvät taustalla olevan ikkunointijärjestelmän toimesta. Nämä tapahtumat välittyvät JVM:n tapahtumien kuljetusmekanismille, joka toimittaa tapahtumat sille komponentille, johon tapahtuma kohdistui. Tätä komponenttia kutsutaan tapahtuman kohdeolioksi. Tämä toimittaminen tapahtuu viimekädessä järjestelmän toimesta kutsumalla kyseisen GUI-komponettiolion metodia dispatchevent(awtevent). Kyseinen metodi ottaa tapahtumaolion vastaan, jonka jälkeen komponentti prosessoi tapahtuman kutsumalla kyseisen olion metodia processevent(awtevent). Muutaman välivaiheen jälkeen prosessointi delegoituu itse tapahtumankäsittelijöiden kutsumiseksi. Tapahtumia varten on useita luokkia ja periaatteessa voitaisiin itsekin määritellä uusia tapahtumaluokkia perimällä, mutta tähän ei yleensä ole mitään tarvetta. Vaikka tapahtumaolioita voidaankin luoda helposti itse, sitäkään ei yleensä tehdä perustilanne on yleensä täysin riittävä: ikkunointiympäristö luo käyttäjän toimenpiteiden seurauksena tapahtumaoliot ja toimittaa ne GUI-komponenteille. Tapahtumien kuljetusmekanismia käsitellään lisää kohdassa 4.4. Ennen siirtymistä tapahtumankäsittelijöiden tarkastelemiseen on paikallaan muistuttaa, että tapahtumia ei prosessoi se säie, jonka avulla käyttäjä luo käyttöliittymän, vaan luonnin seurauksena syntyy erityinen säie, joka ottaa tapahtumia jonosta ja välittää niitä komponenteille kutsuen viime kädessä tapahtumankäsittelijöitä. Huomaa, että kuljetusmekanismi myös suorittaa käsittelijät, joten jos käsittelijän suoritus kestää kauan, kuljetusmekanismi jumittuu! 4.2 Tapahtumat JDK 1.0:ssa tapahtumat mallinnettiin niin, että ne perivät luokalta Event. JDK 1.1:n myötä koko tapahtumankäsittelyn toimintaa muutettiin paljon ja sen seurauksena JDK 1.1:n jälkeen AWT:n tapahtumat ovat perineet luokasta AWTEvent, jonka yliluokkana toimii luokka EventObject. Kuvassa 4.1 on esitetty AWT:n 18 luokkaa/rajapintaa, jotka mallintavat erilaisia tapahtumia. Muutamien kohdalla yksi luokka mallintaa yhdenlaista tapahtumaa, mutta monet mallintavat useaa samantapaista tapahtumaa (esim. erilaisia hiiritapahtumia (MouseEvent) on 7 kappaletta; ja lisäksi on vielä hiiren rullaan liittyviä tapahtumia (MouseWheelEvent)). AWT:n tapahtumat voidaan jakaa ns. matalan tason tapahtumiin (low level event) ja semantti-

35 4.2. TAPAHTUMAT 35 EventObject AWTEvent ActionEvent AdjustmentEvent ComponentEvent ItemEvent TextEvent HierarchyEvent InvocationEvent InputMethodEvent ContainerEvent FocusEvent InputEvent PaintEvent WindowEvent KeyEvent MouseEvent MouseWheelEvent Kuva 4.1: AWT:n tapahtumien hierarkia. siin tapahtumiin (semantic event). Matalan tason tapahtumia edustavat mm. luokkien KeyEvent ja MouseEvent oliot. Kaikkien matalan tason tapahtumien yliluokkana toimii luokka ComponentEvent. Muut luokan AWTEvent aliluokat kuvaavat semanttisia tapahtumia. Terminologinen ero tulee siitä, että matalan tason tapahtumien merkitys siis mitä on tapahtunut on kontektista riippumaton. Semanttisten tapahtumien merkitys puolestaan riippuu siitä, minkä komponentin yhteydessä tapahtuma oikein tapahtuu. Huomaa, että se mitä on tapahtunut ei määrittele, miten ohjelman tai yksittäisen komponentin tulisi reagoida kyseiseen tapahtumaan. Tapahtumankäsittelijä määrittelee merkityksen sille, mitä tapahtuma komponentin yhteydessä oikein tarkoittaa. Eräs yleinen ero matalan tason ja semanttisten tapahtumien välillä on, että käyttäjän aiheuttamat toimenpiteet ovat ensisijaisesti matalan tason tapahtumia matalan tason tapahtuma voi samalla aiheuttaa jonkin semanttisen tapahtuman. Huomaa, että jos matalan tason tapahtuma laukaiseen jonkin semanttisen tapahtuman (tai mahdollisesti useita), sekä matalan tason että semanttiset tapahtumat kulkevat koko tapahtumankäsittelykoneiston läpi. Toinen ero on, että tietynlaisen matalan tason tapahtuman voi kuluttaa, ennen kuin se ehtii aiheuttaa semanttisen tapahtuman. Voisi kuvitella, että kuluttaminen tarkoittaa matalan tason tapahtuman tuhoamista, mutta sitä se ei tarkoita kysymys on vain mahdollisuudesta merkitä tapahtuma kulutetuksi luokasta InputEvent perittävällä metodilla void consume(). Kuvan 4.1 perusteella ainoastaan luokkien KeyEvent ja MouseEvent mukaiset tapahtumat voidaan kuluttaa 1. Kulutettu tapahtuma kulkee kuluttamattomien tapaan koko tapahtumankäsittelymekanismin läpi komponenteissa olevien prosessointimetodien pitää ennen tapahtuman soveltamista käsittelijöihin tarkistaa, onko tapahtuma jo kulutettu. 1 Itse asiassa metodi consume() määritellään jo luokassa AWTEvent, mutta vasta InputEvent toteuttaa sen niin, että tapahtuma voidaan merkitä kulutetuksi.

36 36 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY Jos matalan tason tapahtuma laukaiseen semanttisen tapahtuman, herää kysymys, missä järjestyksessä tapahtumat kulkevat käsittelykoneiston läpi? Vastaus kuuluu, että matalan tason tapahtuma käsitellään ensin. Toisaalta, yksittäinen tapahtuma laukaisee kaikki kohdeolioon liitetyt kyseisen tyyppiset tapahtumankäsittelijät, mutta Java jättää määrittelemättömäksi sen, missä järjestyksessä kyseiset tapahtumankäsittelijät suoritetaan! Kuvan 4.1 perusteella matalan tason tapahtumia varten AWT:ssä on 9 luokkaa. Näistä InputEvent on abstrakti. Tapahtumaluokat esittävät useita ko. tyypin mukaisia tapahtumia. Kutakin erilaista tapahtumaa kohti kutsutaan tiettyä tapahtumankäsittelijän metodia. Taulukossa 4.1 luetellaan, mitä metodeja tapahtumien kohdalla kutsutaan. Seuraavassa lyhyt selostus kustakin matalan tason tapahtumaluokasta. ComponentEvent. Tällä luokalla on kaksi rooli: se toimii matalan tason tapahtumien yliluokkana, mutta samalla se edustaa yleisiä komponenttitapahtumia. Näitä tapahtumia on neljä: komponentin siirto, koon muuttaminen, komponentti piiloon ja esiin. Voisi ajatella, että AWT haluaa näin kertoa, milloin tapahtumankäsittelijän pitää tehdä kyseinen muutos. Tästä ei kuitenkaan ole kysymys. Nämä poikkeukset ovat vain informatiivisia : AWT hoitaa kyseiset komponentteihin kohdistuvat muutokset. Sovelluslogiikalle annetaan vain mahdollisuus reagoida myös tapahtumaan. FocusEvent. Tähän luokkaan liittyy kaksi erilaista tapahtumaa: komponetti saa tai menettää näppäimistöfokuksen. Fokus tarkoittaa sitä, että käyttäjän kirjoittamat merkit kohdistuvat kyseiselle komponentille. Kaikki komponentit eivät voi saada fokusta. Esimerkiksi leimoihin ei kohdistu näitä tapahtumia. WindowEvent. Window-tyyppisiin olioihin (myös siis esim. Frame-olioihin) voi kohdistua näitä tapahtumia. Erilaisia tapahtumia on 7 kappaletta: ikkuna sulkeutumassa, sulkeutunut, avautunut 1. kertaa näkyväksi, muutettu ikoniksi, ikoni avattu, ikkuna tuli aktiiviseksi (näppäimistövirta kohdistuu tähän ikkunaan) ja ikkuna tuli passiiviseksi (näppäimistövirta ei enää kohdistu ko. ikkunaan). MouseEvent. Hiiritapahtumiakin on 7 kappaletta: hiiren nappula alas, ylös, klikkaus (tai useita peräkkäin), hiiri tulee komponentin alueelle, poistuu siltä, hiirtä liikutetaan ja hiirtä raahataan. Tämän luokan olioilla on sisältönä mm. positio ja klikkausten määrä. MouseWheelEvent. Tämä tapahtumaluokka edustaa hiiren "rullan"pyörittämistapahtumia. Sisältönä pyöritysaskelten määrä; +/- = eteenpäin/taaksepäin. (JDK 1.4.) KeyEvent. Tällä luokalla esitetään kolmea erilaista näppäimistötapahtumaa: näppäin pohjaan, ylös ja merkki tuotettu. Tämän lisäksi luokalla on suuri määrä nimettyjä julkisia vakioita, jotka esittävät eri merkkejä; esim. KeyEvent.VK_Z = merkki z. InputEvent. Abstrakti yliluokka hiiri- ja näppäimistötapahtumille. ContainerEvent. Säiliöihin liittyvien tapahtumien luokka, joka esittää kahta tapahtumaa: säiliöön komponentti lisää tai pois. Nämä tapahtumat ovat vain informatiivisia: AWT hoitaa komponentin käsittelyyn liittyvän tekemisen. PaintEvent. Liittyy komponentin piirtämiseen uudelleen. Tätä ei ole tarkoitettu otettavaksi kiinni käyttäjän toimesta.

37 4.2. TAPAHTUMAT 37 Tapahtuma & mitä tapahtui käsittelijä & kutsuttava metodi Matalan tason tapahtumat ComponentEvent ComponentListener - komponentti piiloon - componenthidden(componentevent) - komponentti esiin - componentshown(componentevent) - komponentti siirtyi - componentmoved(componentevent) - komponentin koko muuttui - componentresized(componentevent) FocusEvent FocusListener - saatiin näppäimistöfokus - fucusgained(focusevent) - menetettiin näppäimistöfokus - focuslost(focusevent) WindowEvent WindowListener - sulkeutumassa - windowclosing(windowevent) - sulkeutunut - windowclosed(windowevent) - avautunut 1. kertaa - windowopened(windowevent) - aktiiviseksi - windowactivated(windowevent) - passiiviseksi - windowdeactivated(windowevent) - ikoniksi - windowiconified(windowevent) - ikoni auki - windowdeiconified(windowevent) MouseEvent MouseListener - näppäin alas - mousepressed(mouseevent) - näppäin ylös - mousereleased(mouseevent) - klikkaus - mouseclicked(mouseevent) - hiirikursori tuli komponentin alueelle - mouseentered(mouseevent) - hiirikursori poistui alueelta - mouseexited(mouseevent) MouseMotionListener - hiirikursori liikuu komponentin alueella - mousemoved(mouseevent) - hiirtä raahataan alueella - mousedragged(mouseevent) MouseWheelEvent MouseWheelListener - rullan pyöritys - mousewheelmoved(mousewheelevent) KeyEvent KeyListener - näppäin pohjaan - keypressed(keyevent) - näppäin ylös - keyreleased(keyevent) - merkki tuotettu - keytyped(keyevent) ContainerEvent ContainerListener - komponentti lisätty - componentadded(containerevent) - komponentti poistettu - componentremoved(containerevent) Semanttiset tapahtumat (ei täydellinen luettelo) ActionEvent ActionListener - esim. nappulan painaminen - actionperformed(actionevent) AdjustmentEvent AdjustmentListener - esim. liu utinta siirretty - adjustmentvaluechanged(adjustmentevent) ItemEvent ItemListener - alkio valittu tai poistettu (esim. lista) - itemstatechanged(itemevent) TextEvent TextListener - tekstin sisältö muuttunut - textvaluechanged(textevent) Taulukko 4.1: AWT:n tapahtumia ja niiden seurauksena kutsuttavat metodit.

38 38 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY Semanttisten tapahtumien merkitys tulee esiin niitä tukevien komponenttien yhteydessä. Taulukon 4.1 loppuosassa on lueteltu semanttiset tapahtumat ja niiden seurauksena kutsuttavat metodit. Tapahtumia esittävillä luokilla on muutamia metodeja konstruktorien lisäksi näitä metodeja on kuvattu taulukossa 4.2 muutamien tapahtumien osalta. Lisäksi tulevat luokista EventObject ja AWTEvent perityt metodit. ComponentEvent : Component getcomponent() Palauttaa kohdeolion. WindowEvent : Window getwindow() Palauttaa kohdeolion. InputEvent (abstrakti) : void consume() Kuluttaa tapahtuman. boolean isconsumed() Onko tapahtuma kulutettu. int getmodifiers() Palauttaa lippuvektorin, joka kertoo oliko esim. Shift-näppäin alhaalla. boolean isaltdown() Oliko Alt-painettuna. boolean isaltgraphdown() Oliko AltGraph-painettuna. boolean iscontroldown() Oliko Control-painettuna. boolean isshiftdown() Oliko Shift-painettuna. MouseEvent : int getclickcount() Palauttaa klikkausten lukumäärän. Point getpoint() Palauttaa tapahtuman kohdepisteen suhteessa komponenttiin. int getx() Palauttaa kohdepisteen X-koord. suhteessa komponenttiin. int gety() Palauttaa kohdepisteen Y-koord. suhteessa komponenttiin. boolean ispopuptrigger() Onko tapahtuma Popup-menun laukaisutapahtuma. KeyEvent : char getkeychar() Palauttaa tähän tapahtumaan liittyvän merkin. ContainerEvent : Container getcontainer() Palauttaa kohteena olevan säiliön. Component getchild() Palauttaa säiliön komponentin, johon tapahtuma liittyi. ActionEvent : String getactioncommand() Palauttaa tapahtumaan liittyvän komennon ; sellainen on mahdollista määrätä esim. nappulan painamisen yhteyteen. int getmodifiers() Palauttaa lippuvektorin, joka kertoo oliko esim. Shift-näppäin alhaalla. AdjustmentEvent : Adjustable getadjustable() Palauttaa kohdeolion. int getvalue() Palauttaa kohteen tapahtuman seurauksena saavan arvon. ItemEvent : Object getitem() Palauttaa kohdeolion. int getstatechange() Palauttaa tiedon muutoksen tyypistä: valittu / poistettu. Taulukko 4.2: Muutamia tapahtumaluokkia ja niiden keskeisiä metodeja.

39 4.3. TAPAHTUMANKÄSITTELIJÄT 39 Luokka EventObject toteuttaa rajapinnan Serializable, joten kaikki AWT:n tapahtumat ovat sarjoittuvia. Luokan EventObject konstruktori ottaa Object-tyyppisen kohdeolion 2. Vastaavasti luokalla on havainnointimetodi Object getsource() kohdeolion havainnoimiseksi tämä havainnointimetodi siis periytyy kaikille AWT:n tapahtumille. Taulukkoon 4.3 on koottu luokan AWTEvent keskeiset metodit niitä ei ole montaa. AWTEvent(Event e) Konstruktori, joka saa syötteekseen JDK1.0-tyylisen tapahtuman e. AWTEvent(Object s, int id) Konstruktori, jonka kohteena on olio s; tapahtuman tyyppi on id (kunkin tapahtumaluokan kohdalla määritellään lukuväli, jonka arvot edustavat ko. tyypin mukaisia tapahtumia (ilmeisesti yksi numero per tapahtuman alityyppi; esim. hiiritapahtumia oli 7 tyyppiä ). Object getsource() Funktio, joka palauttaa tapahtuman kohdeolion. int getid() Havannoija, joka palauttaa tapahtuman tyypin. protected void consume() Kuluttaa tapahtuman. Vain InputEvent-tapahtumat voi kuluttaa. protected boolean isconsumed() Havannoija: onko tapahtuma merkitty kulutetuksi? Taulukko 4.3: Luokan AWTEvent konstruktoreita ja metodeja. Taulukon 4.3 metodeista voi päätellä, että tapahtumaolioita voi luoda itse ja niitä voi myös soveltaa komponentteihin saaden tapahtumat laukaisemaan tapahtumankäsittelijöitä. Tällainen menettely ei kuitenkaan vastaa efekteiltään natiivia tapahtuman syntymään ja tapahtuman tulemista osaksi matalantason tapahtumien käsittelyjonoa. Testaus- ja demonstraatiotarkoituksissa on hyvä voida luoda ohjelmallisesti tapahtumia, jotka käyttäytyvät kuten graafisen käyttöliittymän käyttäjän aiheuttamat natiivit tapahtumat. Tätä varten AWT:ssa on luokka Robot, jolla voi luoda natiiveja hiiri- ja näppäintapahtumia. Lisäksi luokalla Robot voi ottaa kuvaruutukaappauksia ja saada aikaan viiveitä luotujen tapahtumien välille. Kyseinen luokka on ensisijaisesti tarkoitettu käyttöliittymien regressiotestausta varten sitä ei käsitellä tässä kohtaa oppimateriaalia vaan vasta luvussa Tapahtumankäsittelijät Graafisten käyttöliittymien pääasiallinen toiminnan ohjauskeino ovat tapahtumankäsittelijät. Komponenteista koostuvan käyttöliittymän luonnin jälkeen sovellus usein 3 siirtyy tilaan, jossa sovelluksen toimintaa ohjataan vain komponentteihin käyttöliittymän kautta (hiiri, näppäimistö) kohdistettavilla tapahtumilla, jotka puolestaan laukaisevat niihin liitettyjä tapahtumankäsittelijöitä. Koska tapahtumankäsittelijät ovat niin keskeisessä roolissa tällaisessa ohjelmoinnissa, käytetään tämänlaisesta ohjelmoinnista nimitystä tapahtumaohjattu ohjelmointi (event-driven programming). Tavallisista peräkkäisistä ohjelmista poiketen huomio on pienten toimintojen, tapahtumankäsittelijäolioiden, ohjelmoinnissa. Mitään yhtä ratkaistavaa ongelmaa ei ole, vaan isommissa sovelluksissa pitää kirjoittaa 2 Taustalla on siis ajatus, että tapahtumat voivat liittyä muihinkin olioihin kuin GUI-komponentteihin. 3 Uusien säikeiden käynnistäminen, esim. animointia varten, muodostaa toisen keinon ohjata sovelluksen toimintaa käyttöliittymän maalaamisen jälkeen.

40 40 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY kymmeniä pienehköjä tapahtumankäsittelijöitä. Tavallaan tapahtumaohjatun ohjelmoinnin vaikeutena voi pitää koodin pirstoutumista yksittäisiin tapahtumankäsittelijöihin. Toisaalta ongelmana on tapahtumankäsittelijöiden yhteistoiminnan hallinta ja itse ohjelmointivaiheessa keskeinen ongelma on päättää, miten tapahtumankäsittelijä pystyy vaikuttamaan ohjelman muihin graafisiin komponentteihin ja käyttöliittymän tilaa kuvaaviin olioihin. Tiedon välittämiseksi on käytettävissä useita keinoja, mutta tapahtumankäsittelijän toteutustapa vaikuttaa käytettävissä oleviin keinoihin. Kuten aiemmin on käynyt ilmi, komponenttiin kiinnitetyn tapahtumankäsittelijän laukaisee ko. komponenttiin kohdistunut tapahtuma ja käsittelijän suorittaminen tarkoittaa tietyn (tapahtumasta riippuen) käsittelijän metodin kutsumista. Kun tähän vielä lisätään, että yleensä tapahtumankäsittelijät kirjoitetaan reagoimaan vain yhdenlaiseen tapahtumaan, tarkoittaa se, että tapahtumankäsittelijäoliot ovat käytännössä simuloituja funktioparametreja 4. Toisinaan tapahtumankäsittelijä toteutetaan niin, että se saa kaiken toiminnan kannalta tarpeellisen informaation parametriensa ja/tai staattisten luokkamuuttujien kautta jolloin tapahtumankäsittelijäolion tietosisältö on tyhjä, sillä ei ole lainkaan instanssimuuttujia! Tuolloin tapahtumankäsittelijä on selkeästi simuloitu funktioparameteri. Tukea funktioparametriajatukselle tulee myös Java-kielen taholta, sillä tapahtumankäsittelijöitä voidaan kirjoittaa ns. anonyymien sisäluokkien (anonymous inner classes) avulla. Tuolloin tapahtumankäsittelijä usein kirjoitetaan konkreettisesti metodin, jolla käsittelijä liitetään komponenttiin, parametriosaan. Seuraavaksi käsitellään eri tapoja toteuttaa tapahtumankäsittelijä perimällä pohtien samalla tapojen etuja ja haittoja. Tapahtumankäsittelijöiden muodon määräävät rajapinnat, joiden metodit esiintyivät jo taulukossa 4.1 kuhunkin käsittelijään liittyy hyvin vähän metodeja. Itse asiassa kaikkien tapahtumankäsittelyrajapintojen yliluokkana toimii EventListener, mutta se on vain merkkausrajapinta, joka ei määrittele yhtään metodia. Kirjoittamisen helpottamiseksi ovat käytettävissä adapteriluokat (jokaista sellaista rajapintaa kohti, jolla käsitellään useita samantapaisia tapahtumia). Tämän kohdan lopuksi on vielä paikallaan huomata, että käsittelijöiden kiinnittäminen komponentteihin (ja poistaminen) on ajonaikaista eli tapahtumankäsittely on Javassa hyvin dynaamista Tapahtumankäsittelijän toteutusvaihtoehdoista Tapahtumankäsittelijät muodostetaan toteuttamalla rajapinta tai perimällä adapteriluokasta (tai erikoistamalla jostakin toteutetusta tapahtumankäsittelijäluokasta). Toteuttamiseen on kuitenkin useita hyvin erilaisia vaihtoehtoja: 1. normaali itsenäinen luokka, 2. (staattinen) sisäluokka, 3. anonyymi (staattinen) sisäluokka tai 4. osana jotain luokkaa, jonka varsinainen rooli on jotain aivan muuta kuin toimia tapahtumankäsittelijän toteuttajana. Anonyymillä sisäluokalla voidaan luoda tapahtumankäsittelijä laittamalla anonyymi sisäluokka välittömästi tapahtumankäsittelijäluokan konstruktorin kutsun yhteyteen seuraavasti: new Yliluokka(... ){... metodien (ja muuttujien määrityksiä)... 4 Jos Java tukisi funktioparametereja, niin ehkäpä tapahtumankäsittelijät olisivat vain yksittäisiä metodeja.

41 4.3. TAPAHTUMANKÄSITTELIJÄT 41 Edellisessä Yliluokka on esimerkiksi MouseAdapter, josta siis tulee aaltosulkujen sisällä määriteltävän tapahtumankäsittelijäluokan yliluokka. Yliluokka voi olla myös rajapinta, esimerkiksi ActionListener! Sisäluokalle muodostuu implisiittisesti konstruktori, jolla on (... ) -osan mukaiset parameterit ja jonka rungossa suoritetaan super(... ). Anonyymillä (ali)luokalla ei voi olla eksplisiittisesti määriteltyä konstruktoria. Tyypillisesti anonyymin sisäluokan mukaisen tapahtumankäsittelijän määrittely sijoitetaan metodin kutsun parametriosaan, kuten esimerkin 4.1 (sivu 44) jälkimmäisessä anonyymissä sisäluokassa. Tuolloin sisäluokan olio on kertakäyttöinen se liitetään komponenttiin ottamatta talteen viittausta siihen. Toisaalta anonyymin sisäluokan mukainen olio voidaan tallettaa myös yliluokan mukaiseen muuttujaan kuten esimerkissä 4.1 myös tehdään. Toteuttamistavan valinnan kannalta olennaista on, miten käsittelijä pääsee käsiksi niihin toimintaympäristönsä olioihin, joihin sen tulisi toiminnallaan vaikuttaa. Tyyppillisesti käsittelijän pitäisi vaikuttaa sovelluksen GUI-komponenttien sisältöön ja järjestelmän tilaa kuvaavaan informaatioon. Esimerkiksi, lomakesovellukseen voidaan luoda nappula, jota klikkaamalla käyttäjän täyttämän lomakkeen tiedot tallennetaan vaikkapa tietokantaan. Tapahtumankäsittelijä on kiinnittynyt nappulaan vaikka se saakin selville kohdeolionsa, sen kautta se ei pääse käsiksi esimerkiksi lomakkeen tekstikenttien sisältöön. Käsittelijän pitää päästä käsiksi niihin, jotta se osaa lukea niiden sisällön (ja vaikkapa nollata sisällön samalla). Lisäksi käsittelijän pitää jotenkin päästä käsiksi myös tietokantaan ja osata käsitellä sitä. Tietokantayhteyden mahdollistavan olion välittämiseen on useita tapoja: 1. konstruktorin parametrina, jota kautta olioviite voidaan helposti siirtää osaksi käsittelijän tietosisältöä; 2. (ulko)luokan staattinen muuttuja, jolloin pitää vain huolehtia siitä, että muuttuja on käsittelijän käytettävissä; 3. (ulko)luokan instanssimuuttujan kautta, jolloin pitää taaskin huolehtia siitä, että käsittelijä pääsee käsiksi muuttujaan; 4. nimetyn vakion kautta viite olioon voidaan tallettaa nimettyyn vakioon, jolloin ongelmana on taas vakion näkyvyys; ja 5. kohdeolion kautta voidaan myös päästä käsiksi tarvittaviin ympäristön olioihin, mutta tämä tyyppillisesti edellyttää, että GUI-komponentista muodostetaan perimällä erikoistettu versio, jonka osaksi kyseiset oliot on mahdollista liittää. Viimeinen keino on varsin epätavallinen ja työläs, koska se edellyttää omien GUI-komponenttien muodostamista. Ensimmäinen keino on suositeltavin, koska silloin käsittelijän riippuvuus ympäristöstään on minimaalinen: kaikki tarpeellinen voidaan välittää konstruktorin kautta. Tuolloin käsittelijä muodostaa selkeän itsenäisen kokonaisuuden ja muutenkin tiedon kapseloinnin hyviä periaatteita on mahdollista noudattaa. Tavat 2 4 ovat itse asiassa myös varsin tavallisia: sisäluokkien, varsinkin anonyymien sisäluokkien, tapauksessa niitä usein käytetään. Anonyymien sisäluokkien kohdalla käyttö on puolusteltua, mutta tavallisten sisäluokkien kohdalla voitaisiin hyvin välttää luokkamuuttujien käyttö. Instanssimuuttujien käyttö on tavallisten sisäluokkien kohdalla luonnollista Muutama neuvo toteutustavan valitsemiseksi Mitä tapaa tulisi sitten käyttää tapahtumankäsittelijän toteuttamiseksi? Jos tapahtumankäsittelijästä on tarkoitus tehdä yleiskäyttöinen sillä on käyttöä useiden sovellusten yhteydessä niin suositeltavinta on tietysti tehdä siitä tavallinen itsenäinen luokka (ks. esimerkki 4.4 sivulla 47). Tapahtumankäsittelijät ovat valitettavasti usein niin sidoksissa sovelluksen logiikkaan, että niitä ei voi pitää

42 42 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY sellaisenaan yleiskäyttöisinä 5 jotakin sovelluksen (isohkoa) osaa toisaalta voidaan hyvinkin uudelleenkäyttää (erityisesti sovellusperheen tapauksessa). Jos on tehty uusi GUI-komponentti erikoistamalla jotakin olemassaolevaa komponenttia ja sen yhteyteen liitetään kiinteästi siihen liittyvä tapahtumankäsittelijä, niin hyvä ratkaisu olisi käyttää sisäluokkia. Tuolloin suuri osa tiedosta voi tulla sisäluokan mukaisen käsittelijäolion käyttöön suoraan siihen liittyvän ulkoluokan mukaisen olion tietosisällöstä (johon käsittelijä automaattisesti pääsee sisäluokan oliona käsiksi). Staattiseen sisäluokkaratkaisuun pitää turvautua, jos esimerkiksi main-metodissa luodaan Frameolio ja rakennetaan sovellus liittämällä GUI-komponentteja siihen. Tuolloin ei ole olemassa ulkoluokan mukaista oliota ja tiedonvälityskeinoina ovat lähinnä luokkamuuttujat ja konstruktorin parameterit. Näistä jälkimmäinen on selvästi suositeltavampi, koska silloin sisäluokasta tulee itsenäisempi. Tilannetta voi verrata itsenäiseen luokkaan, mutta yksityisten staattisten sisäluokkien käyttäminen on kuitenkin suositeltavaa, jos käsittelijän toiminta on niin tiukasti sovellukseen liittyvää, ettei sitä voi pitää yleiskäyttöisenä. Turvautumalla yksityiseen sisäluokkaan, pysyy sovelluksen julkisten luokkien määrä paremmin hallittavana. Anonyymit sisäluokat on esitelty Java-kieleen lähinnä tapahtumankäsittelijöiden toteuttamista varten. Tyypillisesti käsittelijöitä on paljon ja yksi käsittelijä tekee suhteellisen yksinkertaisen asian toteuttaen vain yhden rajapinnan metodeista (usein yliluokkana käytetään adapteria). Tähän tarkoitukseen anonyymit sisäluokat tarjoavat kompaktin mutta samalla valitettavan sekavan tavan koodata tapahtumankäsittelijä. Anonyymeillä sisäluokilla tulisi toteuttaa vain lyhyitä tapahtumankäsittelijöitä usein tällaiset ovat kertakäyttöisiä : ne halutaan vain liittää GUI-komponenttiin ottamatta talteen viitettä käsittelijään. Anonyymin sisäluokan mukaiselle oliolle välitetään tietoa lähinnä vain kohdeolion, instanssi- sekä luokkamuuttujien ja nimettyjen vakioiden kautta. Huomaa, että anonyymi sisäluokka on implisiittisesti staattinen, jos se luodaan staattisen metodin sisällä! Lopuksi, usein tapahtumankäsittelijöitä näkee liitettävän osaksi luokkaa, jonka rooli on jokin aivan muu. Esimerkiksi muodostettaessa omaa applettia perimällä luokasta Applet, voidaan samalla toteuttaa usean tapahtumankäsittelijän rajapinnan metodit ja sitä kautta luotava appletti-olio toimii samalla myös tapahtumankäsittelijänä. Tällaista käytäntöä ei voi pitää suositeltavana. Toteuttamalla käsittelijä sisäluokkana saadaan aikaan aivan sama toiminnallisuus, mutta käsittelijä muodostaa huomattavasti selkeämmän kokonaisuuden. 4.4 Tapahtumankäsittelyn toiminta Miten tapahtumankäsittely on Javassa oikein toteutettu? Tähän kysymykseen ei ole kovin helppo saada vastausta. Vastausta kaipaavat esim. seuraavat kysymykset: missä tapahtumaolio oikein luodaan? mikä on AWT:n GUI-komponenttien ja niiden vastionolioiden suhde tässä mielessä? miten matalan tason tapahtuma aiheuttaa semanttisen tapahtuman? miten tapahtumaoliot oikein kulkeutuvat GUI-komponenteille? eikö hiiren liikkeestä aiheudu aivan hirveä määrä tapahtumia miten ne hoidetaan? 5 Tavallaan tämä on luonnollinen seuraus siitä, että GUI-sovelluksen toiminta toteutetaan juuri tapahtumankäsittelijöillä.

43 4.5. ESIMERKKEJÄ 43 Kun käyttäjä kohdistaa esim. hiirellä jonkin toimenpiteen johonkin komponenttiin, taustalla oleva ikkunointijärjestelmä tunnistaa, tapahtuman ja sen, mihin vastinolioon se kohdistuu. Ikkunointijärjestelmän toimesta vastinolio saa tiedon tapahtumasta AWT:n tapahtumaolio syntyy vastionolion toimesta. Vastionolio lähettää tapahtumaolion AWT:n GUI-komponentille. Olisi mahdollista ajatella, että vastionoliot hoitavat ainoastaan matalan tason tapahtumia ja semanttisten tapahtumien tuottaminen (ja reagointi) tapahtuisi GUI-komponttien toimesta: tuottaminen olisi helppo toteuttaa liittämällä komponentteihin vain tapahtumankäsittelijä, joka kuuntelee matalan tason tapahtumia. Näin ei ole kuitenkaan toimittu, vaan AWT:n semanttiset tapahtumat tuotetaan ilmeisesti myös vastinolion toimesta tuolloin vastionolion pitää ennen semanttisen tapahtuman tuottamista tutkia kuluttaako jokin GUI-komponentin tapahtumankäsittelijä tapahtuman. Itse asiassa vastinolion pitää soveltaa tapahtumaa myös itseensä, mutta ennen sen tekemistä, se antaa GUI-komponenteille mahdollisuuden kuluttaa matalan tason tapahtuma: tähän perustuu esimerkki 4.1. Kun vastionolio lähettää tapahtumaolion GUI-komponentille, tarkoittaa se viime kädessä, että GUI-komponenttiin sovelletaan dispatchevent(... ) -metodia, joka puolestaan kutsuu processevent - metodia tapahtuman käsittelemiseksi. Käytännössä vastinolio ei lähetä tapahtumaoliota suoraan GUIkomponentille, vaan se laitetaan JVM:n ylläpitämään EventQueue-tyyppiseen jonoon. Sieltä erillinen EventDispatchThread-tyyppinen säie hakee tapahtumia ja lähettää ne edelleen GUI-komponenteille. Jonoon voi tulla hyvin paljon tapahtumia nopeammin kuin säie ehtii soveltaa niitä GUI-komponentteihin. Tästä johtuen jonoon liittyy mekanismi, jonka seurauksena tapahtumia voidaan yhdistää. Esimerkiksi, osa käsittelemättömistä hiiritapahtumista voidaan yhdistää (käytännössä poistaa) jäljessä tuleviin tapahtumiin. 4.5 Esimerkkejä Kuvassa 4.2 näkyy GUI-sovellus, jonka keskellä on yksi tekstikenttä ja kummassakin laidassa nappula, jonka avulla niiden sisältö lisätään tekstikentän loppuun. Tekstikenttään voi itse yrittää kirjoittaa merkkejä, mutta vain numeroiden kirjoittaminen (loppuun) on mahdollista. Tämä johtuu siitä, että esimerkissä 4.1 tekstikenttään (muuttujan tf arvo) liitetään tapahtumankäsittelijä, joka reagoi matalan tason KeyEvent-tapahtumiin kuluttaen kaikkien muiden merkkien painalluksen paitsi numeroiden. Huomaa, miten tapahtumankäsittelijä on toteutettu staattisena anonyyminä sisäluokkana, jonka toiminta nojautuu vain tapahtumaolioon. Kumpaankin nappulaan liitetään esimerkissä sama tapahtumankäsittelijäolio, joka tehdään staattisen anonyymin sisäluokan avulla. Tämä tapahtumankäsittelijä tarvitsee toimintaympäristöstään tiedon tekstikenttäoliosta tieto välitetään luokkamuuttujan välityksellä. Huomaa, että vaikka tekstikenttään kohdistuva a :n lisääminen kulutetaankin tapahtumankäsittelijän kautta, nappulan a kautta merkin a lisääminen kuitenkin onnistuu 6. (Esimerkin 4.1 toiminta kaipaisi vielä hiontaa: tekstikenttään voi vain lisätä merkkejä tuhoamismerkkikin suodatetaan pois... ) 6 Ei-numeroiden suodatus pitäisi laittaa alemmas. Näin on mahdollista tehdä Swing:n tapauksessa, jolloin tekstikentän sisältö muodostuu erillisestä mallioliosta.

44 44 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY Kuva 4.2: NumeroKentta-sovellus, jossa suodatetaan tapahtumia. Esimerkki 4.1 Vain numeroita hyväksyvä tekstikenttä tapahtumia kuluttamalla. import java.awt. ; import java.awt.event. ; public class NumeroKentta { private static TextField tf = new TextField(20); private static Button b1 = new Button("1"); private static Button b2 = new Button("a"); public static void main(string[] args) { Frame f = new Frame("NumeroKenttä"); f.setlayout(new BorderLayout()); f.add(b1, BorderLayout.EAST); f.add(b2, BorderLayout.WEST); f.add(tf, BorderLayout.CENTER); Font tr = new Font("TimesRoman", Font.BOLD, 18); f.setfont(tr); ActionListener al = new ActionListener(){ public void actionperformed(actionevent e) { tf.settext(tf.gettext() + e.getactioncommand()); ; b1.addactionlistener(al); b2.addactionlistener(al); tf.addkeylistener(new KeyAdapter(){ public void keytyped(keyevent e) { char c = e.getkeychar(); if (c < 0 c > 9 ) e.consume(); ); f.addwindowlistener(new IkkunanSulkija()); f.pack(); f.setvisible(true); // main // NumeroKentta Monet esimerkit käyttävät IkkunanSulkija-tyyppistä tapahtumankäsittelijää. Se on yleiskäyttöinen ja sen seurauksena sovellus lopetetaan. Koodi on esimerkissä 4.4.

45 4.5. ESIMERKKEJÄ 45 Kuvassa 4.3 havainnollistetaan esimerkkiä 4.2, jossa luodaan itse tapahtuma ja välitetään se GUIkomponentille, keskellä olevalle Lisää -nappulalle. Huomaa, miten tapahtuma luodaan ja välitetään dispatchevent -metodilla komponentille. Tapahtumien ohjelmallinen luonti ja soveltaminen komponentteihin mahdollista sovellusten toiminnallisuuden demonstroinnin laajamittaista demonstrointia varten kannattaa käyttää luokkaa Robot, jota tarkastellaan tarkemmin testauksen yhdeydessä luvussa 12. Esimerkissä 4.2 keskellä olevan nappulan klikkaus aiheuttaa ylhäällä tekstikentässä esitettävän laskurin arvon kasvattavamisen yhdellä. Alimmaksi sijoitettua leimaa voi myös klikata, jolloin siihen liitetty HiiriKuuntelija -tyyppinen käsittelijä laukeaa välittäen tapahtuman keskellä olevalle nappulalle, joka puolestaan kasvattaa laskurin arvoa yhdellä. Huomaa, miten klikkaus otetaan kiinni erilaisilla tapahtumankäsittelijöillä. Kummallekin käsittelijälle välitetään tietoa luokkamuuttujien kautta. Esimerkki 4.2 Tapahtuman luonti ja laukaisu ohjelmallisesti. import java.awt. ; import java.awt.event. ; public class ItseLaukaisu { private static TextField t = new TextField(20); private static int laskuri = 0; private static Button nappula = new Button("Lisää"); public static void main(string[] args) { Frame f = new Frame("ItseLaukaisu"); f.setlayout(new GridLayout(0,1)); f.add(t); Label leima = new Label("Lisää epäsuorasti"); nappula.addactionlistener(new KlikkausKuuntelija()); leima.addmouselistener(new HiiriKuuntelija()); f.add(nappula); f.add(leima); f.addwindowlistener(new IkkunanSulkija()); f.pack(); f.setvisible(true); // main static class KlikkausKuuntelija implements ActionListener { public void actionperformed(actionevent e) { laskuri++; t.settext("laskuri = " + laskuri); // KlikkausKuuntelija static class HiiriKuuntelija extends MouseAdapter { public void mouseclicked(mouseevent e) { nappula.dispatchevent(new ActionEvent(nappula, ActionEvent.ACTION_LAST,"")); // HiiriKuuntelija // ItseLaukaisu

46 46 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY Kuva 4.3: Itselaukaisu-sovellus, jossa tehdään itse tapahtumaolio. Esimerkki 4.3 Kursorin vaihtaminen. import java.awt. ; import java.awt.event. ; public class HiiriTesti { private static TextField t = new TextField("Klikkaa!"); public static void main(string[] args) { Frame f = new Frame("HiiriTesti"); t.seteditable(false); f.add(t); t.addmouselistener(new HiiriKuuntelija()); f.addwindowlistener(new IkkunanSulkija()); f.pack(); f.setvisible(true); // main static class HiiriKuuntelija extends MouseAdapter { public void mousepressed(mouseevent e) { t.setcursor(cursor.getpredefinedcursor (Cursor.WAIT_CURSOR)); public void mousereleased(mouseevent e) { t.setcursor(cursor.getdefaultcursor()); public void mouseclicked(mouseevent e) { if (e.isshiftdown()) t.setcursor(cursor.getpredefinedcursor (Cursor.HAND_CURSOR)); // HiiriKuuntelija // HiiriTesti Lopuksi esimerkillä 4.3 havainnollistetaan, miten hiiren painalluksen yhteydessä voidaan tutkia onko esim. Shift-näppäin alhaalla. Samalla esimerkki havainnollistaa hiiren kursorikuvan vaihtamismahdollisuutta. Kehykseen sijoitetaan yksi tekstikenttä, johon liitetään tapahtumankäsittelijä. Viemällä hiiri tekstikentän päälle, ja painamalla näppäin pohjaan, syntyy tapahtuma, jonka seurauksena kutsutaan käsittelijän metodia mousepressed. Kutsu saa aikaan kursorikuvan muuttumisen. Kun hiiri taas päästetään ylös, syntyy tapahtuma, jonka seurauksena metodin mousereleased kutsu muuttaa

47 4.5. ESIMERKKEJÄ 47 kursorikuvan takaisin normaaliksi. Kokonaisuudesta syntyy vielä klikkaustapahtuma, joka aiheuttaa kursorikuvan muuttumisen käden kuvauksi, jos klikkauksen aikana on Shift ollut painettuna pohjaan. Kehykseen liittyy vielä tapahtumankäsittelijä, joka ikkunan sulkemisen seurauksena lopettaa koko sovelluksen. Esimerkki 4.4 Yleiskäyttöinen käsittelijä sovelluksen lopettamiseksi. import java.awt.event. ; public class IkkunanSulkija extends WindowAdapter { public void windowclosing(windowevent e) { e.getwindow().dispose(); System.exit(0); // IkkunanSulkija

48 48 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY

49 Luku 5 Lisää AWT-komponentteja Tässä luvussa jatketaan luvussa 3 aloitettua AWT:n GUI-komponenttien läpikäyntiä. 5.1 Säiliöiden yliluokka Container Luokka Container määrittelee säiliöiden ominaisuudet toimien kaikkien säiliöluokkien yliluokkana. Container perii luokasta Component, joten säiliöt ovat samalla myös GUI-komponentteja. Säiliön perusominaisuus on, että se koostuu kokoelmasta komponentteja. Komponentit muodostavat listan ja näkyvyysmielessä uudet komponentit lisätään edellisten alle. Komponenttien lisäämiseksi säiliöön luokka Container määrittelee useita add -metodeja. Komponenttien lisäksi säiliöön liittyy sijoittelumanageri (layout manager), josta lisää luvussa 5.4. Luokka Container ei ole abstrakti JDK 1.2:sta eteenpäin (JDK 1.1: abstrakti). Sillä on konstruktori, mutta Container-komponentit ovat keveitä esittäminen vaatii, että ne ovat osa esim. Frame:a. Lähinnä tämän luokan merkitys on määritellä taulukon 5.1 metodien kautta toiminnallisuus muille säiliöluokille. Container() Konstruktori, joka luo tyhjän komponenttisäiliön. Component add(component comp) Lisää komponentin comp (loppuun). Palauttaa comp:n. Component add(component comp, int ind) Lisää komponentin comp kohtaan ind (syrjäyttämättä). Jos ind = -1 = loppuun. Palauttaa comp:n. void add(component comp, Object c) Lisää komponentin comp (loppuun) rajoituksella c. Rajoitus c ohjaa sijoittelumanagerin toimintaa. Esim. merkkijono "South". Informoi sijoittelumanageria. void add(component comp, Object c, int ind) Kuten edellinen, mutta lisäys kohtaan ind. Component add(string name, Component comp) Lisää komponentin comp, johon liittyy nimi (rajoitus) name. Käyttöä ei suositella ( deprecated ). Component getcomponentat(int x, int y) Palauttaa päällimmäisen komponentin, joka sisältää koord. (x, y). Jos ei aluella, null. Jos mikään ei sisällä, palautetaan säiliö itse. Component findcomponentat(int x, int y) Kuten getcomponentat, mutta operoi vain näkyvillä komponenteilla ja soveltaa rekursiivisesti alisäiliöihin. Component getcomponent(int n) Palauttaa n. komponentin säiliöstä. int getcomponentcount() Palauttaa säiliön komponenttien lukumäärän. Component[] getcomponents() Palauttaa säiliön kaikki komponentit taulukkona. JDK 1.5 on tuonut mukanaan suurehkon joukon uusia metodeita (ei käsitellä). 49

50 50 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA boolean isancestorof(component c) Onko c osa säiliötä tai jotain sen alisäiliötä? void remove(component comp) Poistaa säiliöstä komponentin comp. void remove(int ind) Poistaa säiliön ind. komponentin. void removeall() Poistaa säiliön kaikki komponentit. void addcontainerlistener(containerlistener l) Lisää säiliötapahtumien kuuntelijan l. void removecontainerlistener(containerlistener l) Poistaa säiliökuuntelijan l. LayoutManager getlayout() Palauttaa säiliöön liitetyn sijoittelumanagerin. void setlayout(layoutmanager mgr) Asettaa säiliölle sijoittelumanagerin l. void invalidate() Merkitään säiliö (ja kaikki sen yläpuoliset säiliöt) sellaiseksi, että niiden komponenttien sijoittelu pitäisi uusia. void validate() Tekee säiliön komponenttien sijoittelun (rekursiivisesti) uudelleen. Taulukko 5.1: Luokan Container konstruktoreita ja muutamia metodeja. 5.2 GUI-sovellusten perussäiliö Frame ja luokka Window Luokka Window on Container:n välitön aliluokka ja samalla se on GUI-sovellusten yliluokka. Varsinaiset GUI-sovellukset rakennetaan luokkaa Frame käyttäen ja Frame on Window:n aliluokka. Window on ikkuna, jolla ei ole reunoja eikä menupalkkia. Oletuksena se käyttää BorderLayoutsijoittelumanageria. Window on sikäli omituinen luokka, että se on konkreetti, mutta se ei ole itsenäinen, vaan kaikki Window-oliot vaativat tuekseen omistajaolion, käytännössä Frame- tai Dialogtyyppisen olion. Vaikka Window on komponentti, sitä ei saa laittaa toiseen komponenttisäiliöön sama koskee aliluokkia Frame ja Dialog. Ainoa komponenttisäiliö, jonka voi sijoittaa toiseen säiliöön on Paneltyyppinen olio (tai aliluokan mukainen olio, esim. Applet!). Luokka Window määrittelee ikkunoiden yleisiä ominaisuuksia. Muutamia näistä on koottu taulukkoon 5.2. Luokka Frame (kehys) toimii GUI-sovellusten pääluokkana. Sellaisia on vähintään yksi AWTsovelluksessa. Kehysolioilla on reunat (border), otsikkopalkki ja mahdollisuus menupalkkiin (menubar). Oletusarvoisesti kehysolioihin liittyy BorderLayout-sijoittelumanageri. Jos käytettävissä on useita näyttölaitteita (fyysisiä tai virtuaalisia), on mahdollista määrittää, mihin kehysolio liitetään (Window samoin). Ikkunatapahtumien osalta kaikki 7 tapahtumaa ovat mahdollisia Window:n kohdalla mahdollisia ovat vain windowclosed, windowopened. Taulukossa 5.3 on lueteltu Frameluokan metodeja ja konstruktoreita. Window(Frame owner) Luo ikkunan, jonka omistaja on owner. Näkymätön, turvallisuusmanageri voi liittää bannerin osaksi luotavaa ikkunaa (esim. applettien tapauksessa). Window(Window owner) Kuten edellinen, mutta nyt omistaja on owner. void addwindowlistener(windowlistener l) Lisää tapahtumankäsittelijän l. void removewindowlistener(windowlistener l) Poistaa ikkunatapahtumien käsittelijän l. void dispose() Tuhoaa ikkunan ja sen komponentteihin (rekursiivisesti) liittyvät vastinoliot. Vapauttaa niiden muistiresurssin. Oliot ovat edelleen olemassa; identtiset vastinoliot voidaan taas luoda show():lla (tai oikeammin setvisible(true):lla). void tofront() Nostaa ikkunan päällimmäiseksi (graafisen KJ:n ikkunoiden joukossa). void toback() Laittaa ikkunan alimmaksi. void pack() Mukauttaa ikkunan suositeltavaan kokoon (tietoa komponenteilta ja sijoittelumanagerilta). Lisäksi: näkyväksi + validointi.

51 5.2. GUI-SOVELLUSTEN PERUSSÄILIÖ FRAME JA LUOKKA WINDOW 51 void show() Tekee ikkunasta (ja sen komponenteista) näkyvän; tämän sijaan tulisi käyttää Component-luokasta perittyä metodia setvisible. void hide() Tekee ikkunasta näkymättömän; samoin komponenteista rekursiivisesti. final String getwarningstring() Palauttaa ikkunaan mahdollisesti liitetyn varoitustekstin (turvallisuusmanageri). Window getowner() Palauttaa ikkunan omistajan (ikkuna). Window[] getownedwindows() Palauttaa taulukon niistä ikkunoista, joiden (välitön) omistaja tämä ikkuna on. Component getfocusowner() Palauttaa lapsikomponentin, jolla on fokus (ei millään, null). boolean isshowing() Näkyykö ikkuna kuvaruudulla? Taulukko 5.2: Luokan Window konstruktoreita ja muutamia metodeja. Kuva 5.1: Broadcasters-sovellus suorituksessa.

52 52 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA Frame() Konstruktori. Näkymätön ei otsikkotekstiä. Frame(String title) Konstruktori. Näkymätön, otsikko title. String gettitle() Palauttaa otsikkotekstin; "" = ei otsikkoa. void settitle(string title) Asettaa title:n otsikoksi. Image geticonimage() Palauttaa kuvan, jota käytetään ikonisoitaessa ikkuna (X11). void seticonimage(image image) Asetaa ikonisoinnissa käytettävän kuvan image. MenuBar getmenubar() Palauttaa ikkunan menupalkin (null: ei ole). void setmenubar(menubar mb) Asettaa menupalkiksi mb:n. boolean isresizable() Voidaanko ikkunan kokoa muuttaa. Oletus: voidaan. void setresizable(boolean r) Määrää ikkunan koon muutettavuuden; r = true voidaan. void setextendedstate(int state) Määrittää onko ikkuna ikonisoitu vai ei. Mahdolliset arvot: Frame.ICONIFIED, Frame.NORMAL,.... int getextendedstate() Palauttaa ikkunan tilan: ikonina vai ei (+muita). void remove(menucomponent m) Poistaa menupalkista komponentin m. static Frame[] getframes() Palauttaa kaikki kehykset, joita sovelluksen yhteydessä on luotu. Appletin tapauksessa vain ne, jotka ovat ko. applettiin liittyviä. Taulukko 5.3: Luokan Frame konstruktoreita ja metodeja. Seuraavaksi esimerkissä 5.1 (sivu 53) on GUI-sovellus, jossa voidaan luoda dynaamisesti uusia ikkunoita. Samalla ikkunoihin on liitetty toiminnallisuus, jonka kautta tekstikenttään kirjoitettu teksti voidaan välittää kaikille muille ikkunoille. Esimerkin tarkoitus on havainnollistaa ikkunoiden käsittelyä yleensä. Esimerkkiä on havainnollistettu kuvassa 5.1 (sivu 51). Itse sovellus koostuu kahdesta luokasta: pääohjelmasta Broadcasters, jonka tehtävänä on vain luoda yksi Broadcaster -olio ja tehdä siitä näkyvä. Broadcaster -oliot ovat kehyksiä, jotka sisältävät tee uusi - ja ikoneiksi muut - nappulat. Nappuloihin on liitetty tapahtumankäsittelijät, joiden avulla luodaan uusi ikkuna ja muutetaan muut ikkunat ikoneiksi, vastaavasti. Ikoneiksi muuttamisen jälkeen ne pitää avata yksi kerrallaan nappulaa, joka avaisi kaikki, ei ole toteutettu sovellukseen. Broadcaster -oliohin liittyy vielä kaksi tekstikenttää: ylemmän avulla voi lähettää viestin toisille ikkunoille lähetetty viesti näkyy alempana olevassa (estetyssä) tekstikentässä. Keskellä olevaan tekstikenttään on liitetty tapahtumankäsittelijä, joka laukeaa return :n painamisen seurauksena ja käy staattisen getframes -metodin avulla läpi sovelluksen kaikki ikkunat, soveltaen jokaiseen Broadcaster -tyyppiseen olioon metodia setmessage (joka on toteutettu luokan lopussa). Konstruktorin lopussa jokaiseen luotavaan ikkunaan liitetään vielä tapahtumankäsittelijä, joka reagoi ikkunan sulkemistapahtumiin. Ideana on tuhota suljettavan ikkunan vastinolio ja pyrkiä lopettamaan koko sovellus, kun viimeinen ikkuna suljetaan. Ohjelma ei kuitenkaan toimi viimeisen ominaisuuden osalta: Frame.getFrames().length palauttaa sovelluksessa olevien ikkunoiden lukumäärän tämä lukumäärä ei vähene vastinolion tuhoamisen seurauksena (JDK 1.3). Tältä osin korjaaminen on tosin helppoa: pidetään vain kirjaa luotujen ikkunoiden ja suljettujen lukumäärästä, jolloin voidaan päätellä, milloin ollaan sulkemassa viimeistä ikkunaa kokeile korjata esimerkin ohjelmaa! Esimerkissä 5.1 on toteutettu 4 tapahtumankäsittelijää käyttäen anonyymejä sisäluokkia (ei-staattisia). Ensimmäistä ja viimeistä voi kokonsa puolesta pitää hyvinä ratkaisuina, mutta toinen ja kolmas ovat jo liian laajoja niistä olisi ollut järkevää tehdä tavallisia sisäluokkia. Huomaa lopuksi, miten toisessa tapahtumankäsittelijässä käytetään ilmausta Broadcaster.this kyseinen tapahtumankäsittelijä on olio, pelkkä this viittaisi tapahtumankäsittelijäolioon.

53 5.2. GUI-SOVELLUSTEN PERUSSÄILIÖ FRAME JA LUOKKA WINDOW 53 Esimerkki 5.1 GUI-sovellus, jossa luodaan ikkunoita ja välitetään tietoa ikkunasta toiseen. import java.awt. ; import java.awt.event. ; public class Broadcasters { public static void main(string[] args) { Broadcaster b = new Broadcaster("Br",1); b.setvisible(true); // main // Broadcasters class Broadcaster extends Frame { private int counter; private TextField message = new TextField(20); private String otsikko; Broadcaster(String title, int c) { counter = c; otsikko = title; settitle(title); setlayout(new GridLayout(0,2)); Button b1 = new Button("Tee uusi"); Button b2 = new Button("Ikoneiksi muut"); TextField t1 = new TextField(20); message.seteditable(false); add(b1); add(b2); add(new Label("Lähetä")); add(t1); add(new Label("Saatu")); add(message); b1.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { Broadcaster br = new Broadcaster(otsikko+"."+counter,1); counter++; br.setvisible(true); ); b2.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { Frame[] ikkunat = Frame.getFrames(); for (int i=0; i<ikkunat.length; i++) if (ikkunat[i] instanceof Broadcaster) { Broadcaster br = (Broadcaster)ikkunat[i]; if (br Broadcaster.this) br.setstate(frame.iconified); ); t1.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { TextField t = (TextField)e.getSource(); String viesti = t.gettext(); t.settext(); Frame[] ikkunat = Frame.getFrames(); for (int i=0; i<ikkunat.length; i++) if (ikkunat[i] instanceof Broadcaster) { Broadcaster br = (Broadcaster)ikkunat[i]; br.setmessage(otsikko+"says: "+viesti); ); addwindowlistener(new WindowAdapter(){ public void windowclosing(windowevent e){ e.getwindow().dispose(); if (Frame.getFrames().length == 1) System.exit(0); ); pack(); // Broadcaster void setmessage(string msg) { message.settext(msg); // Broadcaster

54 54 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA 5.3 Komponenttien säiliö Panel Luokka Panel (paneeli) on yksinkertainen komponenttien säiliö, joka perii suoraan luokalta Container. Se toimii yliluokkana luokalle Applet. Paneelin tarkoitus on mahdollistaa komponenttien hierarkkinen ryhmittely. Muihin komponenttisäiliöihin verrattuna paneelilla on tärkeä ominaisuus: se on komponentti ja sen saa sijoittaa toiseen säiliöön. Oletusarvoisesti paneelissa on käytössä FlowLayoutsijoittelumanageri. Luokalla Panel on perusmuotoinen konstruktori Panel(), joka tekee tyhjän komponenttisäiliön varustettuna FlowLayout-sijoittelumanagerilla. Konstruktorista on ylikuormitettu versio, jossa sijoittelumanageri annetaan. Muut metodit on peritty luokista Component ja Container (tosin joitakin on hieman muutettu perinnän yhteydessä). 5.4 Sijoittelumanagerit AWT:n osana on (ainakin) 5 sijoittelumanageria. Ne on ryhmitelty rajapintojen LayoutManager ja LayoutManager2 alle, kuten kuvassa 5.2 esitetään. Sijoittelumanagerit ovat seuraavat: BorderLayout: Määrittelee 5 kohtaa (itä, länsi, etelä, pohjoinen, keskellä), jonne komponentteja voidaan sijoitella. CardLayout: Toteuttaa sijoittelumanagerin, johon sijoitetut komponentit ovat kuin päällekkäiset kortit. Flowlayout: Komponentteja sijoitetaan säiliöön vapaan virran tapaan rivi kerrallaan ylhäältä alas, rivillä vasemmalta oikealle. GridLayout: Kaksiulotteinen ristikko, jossa kaikille komponenteille varataan yhtä paljon tilaa. GridBagLayout: Edellisen joustavampi ja monipuolisempi versio. LayoutManager FlowLayout LayoutManager2 GridLayout BorderLayout CardLayout GridBagLayout Kuva 5.2: Sijoittelumanageriluokkien luokkahierarkia. Kuvassa 5.2 esiintyvät rajapinnat määrittelevät sijoittelumanagereille muutamia metodeja, esimerkiksi addlayoutcomponent, removelayoutcomponent, layoutcontainer,.... Näitä metodeita ei ohjelmoijan ole kuitenkaan tarkoitus kutsua, vaan komponentin sijoittaminen säiliöön (vastaavasti poistaminen) tekee automaattisesti tällaisen kutsun. Tärkeä ero luokkien LayoutManager ja Layout- Manager2 välillä on, miten sijoittelumanagerille kerrotaan, mihin tai miten komponentti kuuluisi

55 5.4. SIJOITTELUMANAGERIT 55 sijoitella. Luokan LayoutManager-tapauksessa tätä informaatiota ei ole (järjestykseen perustuva toiminta). Luokka LayoutManager2 yleistää tätä siten, että komponentteihin liittyy sijoittelua ohjaava rajoitus: tämä rajoitus on yksinkertaisimmillaan merkkijono, mutta luokan GridBagLayout rajoitus kerrotaan erityisellä oliolla. Ero tulee esiin metodissa addlayoutcomponent, joka on muotoa void addlayoutcomponent (Component c, Object constraints) Säiliö ilman sijoittelumanageria Luokan Container metodilla setlayout asetetaan sijoittelumanageri säiliölle, mutta sen avulla on myös mahdollista poistaa sijoittelumanageri komponentista. Tämä tapahtuu käyttämällä kutsussa parametria null. Kun säiliöllä ei ole sijoittelumanageria, pitää jokaisen komponentin yhteydessä ilmoittaa komponentin sijainti (pikseleinä) ja koko. Komponentit naulataan kiinteästi kyseisten määreiden avulla säiliöön. Säiliön koon muuttaminen ei tuolloin vaikuta komponenttien sijoitteluun. Tällaisen tavan hankaluutena on edellisten määreiden tarkka laskeminen kaikille sijoiteltaville komponenteille, mutta toisaalta käyttöliittymän ulkoasusta saadaan varmemmin juuri sellainen kuin halutaan Luokka GridLayout Luokan GridLayout mukainen sijoittelumanageri määrittelee säiliöön x y- ristikon, johon sijoitellaan komponentteja ylhäältä alas ja vasemmalta oikealle lisäysjärjestyksessä. Sijoittelumanageri antaa kaikille ruuduille yhtä paljon tilaa. Konstruktorin yhteydessä voi määrittää rivien ja sarakkaiden lukumäärän. Entä jos komponentteja liikaa? Sijoittelumanageri on joustava sarakkeiden määrä joustaa tarvittaessa. Alla olevassa taulukossa 5.4 on kuvattu joitakin konstruktoreita ja metodeja. GridLayout() Konstruktori, yksi rivi, rajattomasti alkioita. GridLayout(int x, int y) Konstruktori, joka määrittelee x riviä ja y saraketta. Toinen voi olla nolla, jolloin se on kooltaan rajoittamaton. GridLayout(int x, int y, int hgap, int vgap) Konstruktori; kuten edellinen; nappuloiden ympärillä tila hgap (vaaka) ja vgap (pysty). Poikkeus IllegalArgumentException, jos hgap tai vgap laiton. Edellä astetuille arvoille on havainnointimetodit: getcolumns, getrows, gethgap, getvgap. Samoin modifiointimetodit: setcolumns, setrows, sethgap, setvgap. Taulukko 5.4: Luokan GridLayout konstruktoreita ja metodeja Luokka FlowLayout Ideana on sijoitella komponentteja riveittäin, vasemmalta oikealle, niin monta kuin yhdelle riville mahtuu. Oletusarvoisesti keskittää kunkin muodostetun rivin. Mahdollista tasata myös jompaan kumpaan reunaan (vakiot FlowLayout.CENTER, LEFT, RIGHT). Komponentit saavat esiintyä eri kokoisina (preferred size).

56 56 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA FlowLayout() Konstruktori; sovelletaan oletusarvoisesti gap-arvoa 5 molempiin suuntiin. FlowLayout(int align) Edellisen lisäksi rivit orientoidaan align:n mukaan. FlowLayout(int align, int hgap, int vgap) Konstruktorin muoto, jolla voidaan asettaa myös gap-arvot. Edellisille ominaisuuksille on lisäksi get- ja set-metodit. Taulukko 5.5: Luokan FlowLayout konstruktoreita ja metodeja Luokka BorderLayout Järjestää komponentit viiteen alueeseen: BorderLayout.NORTH, EAST, SOUTH, WEST ja CEN- TER. Vaikka onkin tiedossa, mikä merkkijono edellisten vakioiden arvona on, tulisi silti käyttää edellisiä luokan BorderLayout nimettyjä vakioita. Oletusalueena toimii BorderLayout.CENTER. Komponentit saavat luonnollisen kokonsa (preferred size), mutta säiliön rajoja pitää noudattaa. (NORTH ja SOUTH: venyvät vaakasuunnassa; EAST ja WEST: pystysuunnassa; CENTER: voi täyttää vapaan tilan.) BorderLayout() Konstruktori, ei tilaa komponenttien välissä. BorderLayout(int hgap, int vgap) Konstruktori, jonka yhteydessä komponenttien reunoille määritellää tyhjää gap-arvojen verran. Taulukko 5.6: Luokan BorderLayout konstruktoreita ja metodeja Luokka CardLayout Luokan CardLayout ajatuksena on tulkita komponentit korteiksi, joista päällimmäinen on aina näkyvillä. Päällimmäisenä toimii alunperin ensimmäinen säiliöön lisätty komponentti. Järjestyksen suhteen AWT:n kuvaus on epämääräisempi. Sen sanotaan määräytyvän säiliön sisäisen järjestyksen mukaan käytännössä järjestys on lisäysjärjestys. Komponentteihin voidaan kuitenkin liittää nimi ja sen perusteella voidaan nostaa nimetty kortti päällimmäiseksi. Taulukossa 5.7 on luokan CardLayout konstruktoreita ja metodeja. CardLayout() Konstruktori, ei tilaa reunoilla. CardLayout(int hgap, int vgap) Määrittää myös tilan. void first(container parent) Ensimmäinen esiin. void next(container parent) Syklisesti seuraava. void previous(container parent) Syklisesti edellinen. void last(container parent) Viimeinen esiin. void show(container parent, String name) Komponentin nimen avulla uusi kortti päällimmäiseksi. Taulukko 5.7: Luokan CardLayout konstruktoreita ja metodeja. Esimerkissä 5.2 käytetään CardLayout-sijoittelumanageria. Säiliöön sijoitetaan kolme komponenttia, joihin sijoittamisen yhteydessä liitetään nimet (merkkijonoja). Nämä samat nimet laitetaan osaksi alaosaan sijoitettavaa pudotusvalikkoa ja valikkoon liitetään tapahtumankäsittelijä, joka valinnan perusteella nostaa nimen mukaisen komponentin päällimmäiseksi.

57 5.4. SIJOITTELUMANAGERIT 57 Kuva 5.3: Sijoittelumanageri CardLayout:n havainnollistusta. Esimerkki 5.2 CardLayout-sijoittelumanagerin havainnollistus. import java.awt. ; import java.awt.event. ; public class CardTest { private static Panel p = new Panel(); public static void main(string[] args) { Frame f = new Frame("CardLayout kokeilu"); f.setsize(200,200); f.setlayout(new BorderLayout()); p.setsize(150,150); p.setlayout(new CardLayout()); Button b1 = new Button("Nappula"); Button b2 = new Button("Toinen"); TextField t = new TextField("Heips"); Choice c = new Choice(); c.add("eka"); c.add("toka"); c.add("kolmas"); p.add(b1,"eka"); p.add(t,"toka"); p.add(b2,"kolmas"); f.add(p); f.add(c, BorderLayout.SOUTH); c.additemlistener(new ItemListener(){ public void itemstatechanged(itemevent e) { ((CardLayout)p.getLayout()).show(p,(String)(e.getItem())); p.validate(); ); f.addwindowlistener(new IkkunanSulkija()); f.setvisible(true); // CardTest

58 58 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA 5.5 Liukuvalitsin Scrollbar Scrollbar on tuttu liu utin, joka voi olla vaaka- tai pystysuuntainen. Liu utinta liikutettaessa siihen liittyvä arvo muuttuu arvo liikkuu kokonaislukuvälillä. Sitä voidaan liikuttaa tarttumalla palkkiin, painamalla reunoissa olevia nuolia (yksikkösiirtymä) sekä PageUp- ja PageDown-näppäimillä (lohkosiirtymä). Liukuvalitsimeen liittyen, voidaan asettaa alueen koko, palkin koko, nykyinen arvo ja siirtymien koot. Arvon muuttuminen aiheuttaa AdjustmentEvent:n muutoksen, tekotapaa (edellä) heijastaen erilaisia tapahtumia on 5 kappaletta. Scrollbar() Konstruktori; arvoväli ; aluksi 0:ssa; siirtymien koot 1 (yksikkösiirtymä) ja 10 (lohkosiirtymä); pystysuuntainen. Scrollbar(int ori) Konstruktori; kuten edellinen, mutta liukuvalitsimen orientaatio on ori. Arvot: Scrollbar.VERTICAL ja Scrollbar.HORIZONTAL. Scrollbar(int ori, int v, int vi, int min, int max) Konstruktori; min... max; aluksi v:ssä; palkin koko vi (samalla lohkosiirtymä). Jos max < min, max := min. Jos v ei alueella, tuodaan alueen lähimpään reunaan. int getorientation() Palauttaa orientaation. int getvalue() Nykyinen arvo; mitataan palkin vasemmasta reunasta. Saa arvoja min... max palkki. int getminimum() Palauttaa pienimmän arvon. int getmaximum() Palauttaa suurimman arvon. int getvisibleamount() Palkin koko. void setorientation(int ori) Asettaa orientaation (setvalues.). void setvalue(int nval) Asetetaan nykyinen arvo (setvalues). void setminimum(int nmin) Asettaa uuden minimin (setvalues). void setmaximum(int nmax) Asettaa uuden maksimin (setvalues). void setvisibleamount(int nvis) Asettaa palkin koon. void setvalues(int nval, int nvis, int nmin, int nmax) Asetetaan kaikki kerralla; pitäisi käyttää tätä eikä yksittäisiä metodeja. void setunitincrement(int v) Asettaa yksikkölisäyksen. int getunitincrement() Yksikkölisäyksen koko. void setblockincrement(int v) Asettaa lohkolisäyksen. int getblockincrement() Lohkolisäyksen koko. Taulukko 5.8: Luokan Scrollbar konstruktoreita ja metodeja. Esimerkki 5.3 havainnollistaa liukuvalitsimen luontia, havainnointia ja arvojen asettamista. Ideana esimerkissä on, että käyttäjän pitäisi asettaa liukuvalitsin pyydettyyn kohtaan parhaansa mukaan, ja sen tehtyään kiinnittää valitsimen arvo painamalla kunkin rivin vasemmassa reunassa olevaa nappulaa. Liukuvalitsimissa ei ole tapahtumankäsittelijää, vain nappuloissa. Huomaa, miten esimerkissä liukuvalitsimet sijoitetaan paneeliin ja nappuloihin liitettäville tapahtumankäsittelijöille kerrotaan konstruktorin kautta, millä liukuvalitsimella operoida. Resetoinnin yhteydessä paneelin komponentteja haetaan järjestysnumeron perusteella.

59 5.5. LIUKUVALITSIN SCROLLBAR 59 Esimerkki 5.3 Scrollbar:n havainnollistus. import java.awt. ; import java.awt.event. ; public class LiukuvalitsinKilpailu { private static final int TESTEJÄ = 4; private static TextField tulos = new TextField(20); private static int[] arvuuteltavat = new int[testejä]; private static int[] chosen = new int[testejä]; private static int cc = 0; private static int score = 0; private static Panel p = new Panel(); public static void main(string[] args) { Scrollbar[] valitsimet = new Scrollbar[TESTEJÄ]; Frame f = new Frame("Liukuvalitsin kilpailu"); f.setsize(800,testejä 75); f.setlayout(new BorderLayout()); p.setlayout(new GridLayout(0,1)); for (int i=0; i<testejä; i++) { Panel p1 = new Panel(); p1.setlayout(new FlowLayout(FlowLayout.LEFT)); Scrollbar s = new Scrollbar(Scrollbar.HORIZONTAL); s.setsize(250,30); s.setvalues(0,3,0,200); valitsimet[i] = s; arvuuteltavat[i] = 0; Button b = new Button("Tee valinta"); b.addactionlistener(new ValintaKuuntelija(s, i)); p1.add(b); p1.add(new Label("Kohdearvo 200:")); p1.add(s); p.add(p1); resetoi(p); Button br = new Button("Resetoi"); br.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { resetoi(p); ); f.add(br, BorderLayout.WEST); f.add(new Label("Yritä asettaa valitsimet kohdearvoihin"), BorderLayout.NORTH); f.add(p, BorderLayout.CENTER); f.add(tulos, BorderLayout.SOUTH); tulos.seteditable(false); f.addwindowlistener(new IkkunanSulkija()); f.setvisible(true); // main private static void resetoi(panel p) { score = 0; cc = 0; for (int i=0; i<testejä; i++) { int max = (int)(math.random() ); // int nro = (int)(math.random() max); int nro2 = (int)(math.random() max); arvuuteltavat[i] = nro; Panel p1 = (Panel)(p.getComponent(i)); Button b = (Button)p1.getComponent(0); b.setenabled(true); ((Label)(p1.getComponent(1))).setText("Kohdearvo "+nro+"(0.."+max+"):"); Scrollbar s = (Scrollbar)(p1.getComponent(2)); s.setsize(max+50,30); s.setenabled(true); s.setvalues(nro2,3,0,max+2); // resetoi static class ValintaKuuntelija implements ActionListener { private Scrollbar valitsin; private int ind; public ValintaKuuntelija(Scrollbar s, int i) { valitsin = s; ind = i; public void actionperformed(actionevent e) { chosen[ind] = valitsin.getvalue(); cc++; score += Math.abs(chosen[ind] - arvuuteltavat[ind]); valitsin.setenabled(false); Button b = (Button)(e.getSource()); b.setenabled(false); if (cc == TESTEJÄ) tulos.settext("tulos on "+score); //actionperformed // ValintaKuuntelija // LiukuvalitsinKilpailu

60 60 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA 5.6 Valintalista List Kuva 5.4: Liukuvalitsimen havainnollistamista. Valintalista toteutetaan luokan List avulla. List on monipuolisempi kuin Choice. Listalla on esitysikkuna, jolla on säädettävä koko. Voidaan luoda kahdenlaisia listoja: (a) yksi alkio valittavissa tai (b) useita alkioita voidaan valita. Listaan liittyy tapahtuma ItemEvent (alkio valittu, valinta poistettu) ja ActionEvent (valittua alkiota kaksoisklikattu; tai enter). Luokkaan Choice verrattuna, metodi ItemEvent.getItem() palauttaa järjestysnumeron! Esimerkki 5.4 havainnollistaa valintalistan toimintaa saman tapaan kuin esimerkissä 3.3 havainnollistettiin luokkaa Choice. Nyt kuitenkin on mahdollista valita useita alkioita samanaikaisesti. Kuva 5.5: Valintalistan havainnollistamista. List() Tyhjä lista; ikkunan koko 4. Sallii vain yhden valittavan. List(int r) Tyhjä lista; ikkunan koko r. Vain yksi valittava. List(int r, boolean mmode) Kuten edellä; mmode = true voidaan valita useita. int getitemcount() Alkioiden määrä listassa. String getitem(int ind) Palauttaa alkion listan kohdasta ind. String[] getitems() Palauttaa listan kaikki alkiot. void add(string item) Lisää alkion item listan loppuun. void add(string item, int ind) Lisää kohtaan ind (syrjäyttämättä). void replaceitem(string item, int ind) Korvaa kohdan ind alkion item:lla. void removeall() Tyhjentää listan. void remove(int pos) Poistaa alkion kohdasta pos. int getselectedindex() Valitun alkion positio, -1: ei yhtä valittua alkiota. int[] getselectedindexes() Palauttaa kaikkien valittujen alkioiden indeksin.

61 5.6. VALINTALISTA LIST 61 String getselecteditem() Palauttaa valitun alkion; null: ei yhtä arvoa. String[] getselecteditems() Palauttaa kaikki valitut alkiot. boolean isindexselected(int ind) Onko ind valittu? int getrows() Palauttaa ikkunan koon. boolean ismultiplemode() Onko monivalintatilassa? void setmultiplemode(boolean b) Asettaa monivalintatilan. Taulukko 5.9: Luokan List konstruktoreita ja metodeja. Esimerkki 5.4 List:n havainnollistus. import java.awt. ; import java.awt.event. ; import java.applet. ; public class ListTesti extends Applet { private TextField tf_in = new TextField(20); private TextField tf_out = new TextField(20); private String[] alkiot = { "Suomen markka", "Ruotsin kruunu", "Norjan kruunu", "Saksan markka", "Venäjän rupla", "Ranskan frangi" ; public void init() { setlayout(new GridLayout(0,1)); add(new Label("Valitse jokin alkio listasta.")); List c = new List(3, true); for (int i=0; i<alkiot.length; i++) c.add(alkiot[i]); c.additemlistener(new ListaKuuntelija()); add(c); add(tf_in); add(tf_out); // init class ListaKuuntelija implements ItemListener { public void itemstatechanged(itemevent e) { String kohde = alkiot[((integer)(e.getitem())).intvalue()]; if (e.getstatechange() == ItemEvent.SELECTED) { tf_in.settext("valittu: " + kohde); else { tf_out.settext("poistettu: " + kohde); // itemstatechanged // ListaKuuntelija // ListTesti

62 62 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA 5.7 Valintalaatikko CheckBox ja radiopainikkeet Valintalaatikko CheckBox on tekstileima, johon liittyy tila ja tilalaatikko: on / off, rastitettu tai ei rastitettu. Valintalaatikoita voidaan ryhmitellä CheckboxGroup-olion avulla ryhmiksi, joista vain yksi ko. ryhmän alkioista voi olla kerrallaan valittuna. Tällaisista valintalaatikoiden ryhmästä käytetään nimitystä radiopainikkeet tai radionappulat. Samaan ryhmään kuuluvien ei tarvitse olla sijoiteltu lähekkäin toisiaan nappulat voivat hyvin olla vaikkapa eri ikkunoissa. Radionappuloiden avulla on tarkoitus valita yksi alkioista tapahtuma ItemEvent: alkio valittu, valinta poistettu. Checkbox() Tyhjä valintalaatikko (off-tilassa). Checkbox(String label) Valintalaatikko leimalla label (off). Checkbox(String label, boolean s) Leima label ja tila s. Checkbox(String label, boolean s, CheckboxGroup g) Kuten edellä; kuuluu radionappuloiden ryhmään g. Checkbox(String label, CheckboxGroup g, boolean s) Kuten edellä. Object[] getselectedobjects() Palauttaa 1-pituisen listan, jossa leima (valittu) tai null (ei valittu). Havainnointimetodit: getlabel, getstate, getcheckboxgroup. Modifiointimetodit: setlabel, setstate, setcheckboxgroup. Taulukko 5.10: Luokan CheckBox konstruktoreita ja metodeja. Luokan CheckboxGroup-olio edustaa vain ryhmän tunnusta. Järjestelmä takaa, että yhdestä ryhmästä voi valita vain yhden valintalaatikon. Huomaa, miten CheckBox:n kohdalla kerrotaan, mihin ryhmään nappula kuuluu. CheckboxGroup() Ainoa konstruktori: luo tyhjän olion, jonka identiteettiä käytetään ryhmien erottelussa. Checkbox getselectedcheckbox() Palauttaa sen valintalaatikon ryhmästä, joka on valittuna; null: ei mitään valittuna. void setselectedcheckbox(checkbox box) Asettaa box:n valituksi (muut off). Jos box == null, kaikki off-tilaan. Jos ei kuulu ryhmään, ei tapahdu mitään. Taulukko 5.11: Luokan CheckBoxGroup konstruktoreita ja metodeja. Esimerkissä 5.5 havainnollistetaan erilaisia valintalaatikoita. Sovellus luo kolme ikkunaa. Kuvan 5.6 alin ikkuna sisältää vain yksittäisiä valintalaatikoita, jotka voidaan valita toisistaan riippumatta. Ohjelmassa tämä kehys luodaan muuttujaan f1. Metodin main lopussa ikkunaan liitetään vielä IkkunanSulkija-tyyppinen tapahtumankäsittelijä. Kuvassa 5.6 vasemmassa yläreunassa oleva ikkuna luodaan main -metodissa muuttujaan f2. Nyt kaikki ovat radionappuloita, jotka kuuluvat samaan ryhmään (ryhmä luodaan muuttujaan g ). Oikeassa yläkulmassa oleva ikkuna on hieman monimutkaisempi. Siihenkin luodaan radionappuloita, jotka kaikki aluksi kuuluvat g1 :n ryhmään. Kuhunkin radionappulaan liitetään nyt tapahtumankäsittelijä, joka on toteutettu luokalla ValintaKuuntelija. Ideana on, että voitaisiin valita vaihtoehdoista korkeintaan 2. Tämän tapahtumankäsittelijä toteuttaa vaihtamalla ensimmäisen valinnan tapahtuessa ko. radionappulan ryhmää se laitetaan yksin omaan ryhmäänsä, jolloin perusryhmästä voidaan vielä valita yksi nappula. Poistotilanteet hoitetaan vastaavasti.

63 5.7. VALINTALAATIKKO CHECKBOX JA RADIOPAINIKKEET 63 Esimerkki 5.5 Checkbox:n havainnollistus. import java.awt. ; import java.awt.event. ; public class CheckboxTesti { private static Checkbox selection1 = null; private static Checkbox selection2 = null; private static CheckboxGroup g1 = new CheckboxGroup(); private static CheckboxGroup g2 = new CheckboxGroup(); public static void main(string[] args) { Frame f1 = new Frame("Yksittäisiä valintalaatikoita"); f1.setsize(200,100); f1.setlocation(300,200); f1.setlayout(new GridLayout(0,1)); f1.add(new Checkbox("1+1 = 2?")); f1.add(new Checkbox("Markka vielä maaliskuussa 2002?")); f1.add(new Checkbox("Kaikki NonStop:t kerättynä?")); f1.pack(); f1.setvisible(true); Frame f2 = new Frame("Ns. radiopainikkeet"); f2.setsize(200,100); f2.setlocation(300,0); f2.setlayout(new GridLayout(0,1)); CheckboxGroup g = new CheckboxGroup(); f2.add(new Label("Kirka levyttänyt?")); f2.add(new Checkbox("Wonders in the night", g, false)); f2.add(new Checkbox("Trains in the night", g, true)); f2.add(new Checkbox("Strangers in the night", g, false)); f2.add(new Checkbox("People in the night", g, false)); f2.pack(); f2.setvisible(true); Frame f3 = new Frame("Kahden valitseminen"); f3.setlayout(new GridLayout(0,1)); f3.add(new Label("Missä elämän merkkejä?")); Checkbox c1,c2,c3,c4; c1 = new Checkbox("Mars", g1, false); c2 = new Checkbox("Venus", g1, false); c3 = new Checkbox("Jupiter", g1, false); c4 = new Checkbox("Maa", g1, false); f3.add(c1); f3.add(c2); f3.add(c3); f3.add(c4); ItemListener l = new ValintaKuuntelija(); c1.additemlistener(l); c2.additemlistener(l); c3.additemlistener(l); c4.additemlistener(l); f3.pack(); f3.setvisible(true); f1.addwindowlistener(new IkkunanSulkija()); // main static class ValintaKuuntelija implements ItemListener { public void itemstatechanged(itemevent e) { Checkbox c = (Checkbox)(e.getItemSelectable()); if (e.getstatechange() == ItemEvent.SELECTED) { // valittu if (selection1 == null) { c.setcheckboxgroup(g2); c.setstate(true); selection1 = c; else { selection2 = c; else { // valinta purettu if (c == selection1) { c.setstate(false); c.setcheckboxgroup(g1); selection1 = null; if (selection2 null) { selection1 = selection2; selection1.setcheckboxgroup(g2); selection1.setstate(true); selection2 = null; else if (c == selection2) { c.setstate(false); selection2 = null; // itemstatechanged // ValintaKuuntelija // CheckboxTesti

64 64 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA Kuva 5.6: Erilaisia valintalaatikoita ja radionappuloita. 5.8 Dialogi-ikkuna Dialog Sovelluksissa on tilanteita, jolloin käyttäjältä pitää kysyä koko sovelluksen jatkotoimintaa ohjaavaa tietoa esimerkiksi, peli on saatu pelattua loppuun ja pitäisi päättää halutaanko pelata uudestaan. Tietokannan kohdalla tilanne voi olla, että on syötetty tietoja tietokantaan ja yritetään syöttää samannimistä alkiota uudestaan, jolloin esimerkiksi pitäisi päättää korvataanko vanha uudella vai ei. Dialogit, keskustelut, ovat juuri tällaista käyttötarkoitusta varten niiden avulla koko sovelluksen toiminta voidaan estää, kunnes käyttäjä on ottanut kantaa dialogin kysymykseen. Yksinkertaisimmassa muodossa dialogeja käytetään asioiden tiedoittamiseen käyttäjälle käyttäjän halutaan vain kuittaavan tiedoitus luetuksi. Luokka Dialog perii luokasta Window. Se on itsenäisten ikkunoiden luokka, joiden avulla tehdään ns. dialogeja eli pyydetään käyttäjältä jotain tietoa / valintaa. Tarvittaessa kaikkien muiden GUI:n osien toiminta estyy: modaaliset (valtaavat koko huomion ) ja ei-modaaliset dialogit. Eimodaalinen dialogi on rinnastettavissa tavalliseen kehykseen. Dialogeihin liittyy otsikko ja reunat. Oletusarvoisesti dialogi on ei-modaalinen ja sen sijoittelumanageri on BorderLayout. Dialogin käsitteleminen perustuu näkyvyyteen. Dialogi tulee käsitellyksi, kun se asetetaan näkymättömäksi (setvisible(false)). Dialogi-ikkuna (yleensä) itse asettaa itsensä lopuksi näkymättömäksi jokin dialogiin kiinnitetty tapahtumankäsittelijä tekee sen. Vastaavasti dialogi esitetään nostamalla se näkyväksi. Dialogin tällaisen käyttämisen takana on ajatus siitä, että yhtä dialogia voidaan käyttää monta kertaa. Dialogeilla tulee olla omistaja : Frame-olio tai Dialog-olio viime kädessä tarvitaan siis vähintään yksi Frame-olio. Omistajaan liittyvä ajatus on, että modaalinen dialogi estää omistajan toiminnan. Unix:ssa dialogi-ikkuna avautuu ja sulkeutuu omistajan mukana (ohjaamana). Dialogien käyttötarkoituksena voi pitää seuraavia asioita: toiminnan valinta, tiedotus ja tiedon hankkiminen. Sovelluksen toiminnan kannalta dialogit ovat hetkellisiä esillä vain kun sovellus on tietyssä tilassa. Dialogilla ei ole minimoi ja maksimoi nappuloita (ikkunan yläkulmassa). Luokkaa Dialog käytetään usein perimällä, koska käyttäjä haluaa määritellä mitä komponentteja erityisesti nappuloita dialogissa on ja mitä toiminnallisuutta dialogiin liittyy. Asiakassuhteen kautta

65 5.8. DIALOGI-IKKUNA DIALOG 65 käyttäminen on toki mahdollista, mutta yleensä räätälöitävää on niin paljon, että on selvempää tehdä dialogista kokonaisuus, luokka. Toisaalta Dialog:n aliluokasta FileDialog ei yleensä enää peritä, koska sen toiminta on tarpeeksi monipuolista sellaisenaan. Dialog(Dialog o) Konstruktori; tyhjä otsikko; omistaja o. Dialog(Dialog o, String t) Kuten edellinen; otsikko t. Dialog(Dialog o, String t, boolean m) Kuten edellinen; modaalisuus m. Dialog(Frame o, String t, boolean m) Kuten edellinen; omistaja Frame-olio o. Variaatioita. Havainnointimetodit: gettitle, ismodal ja isresizable. Modifiointimetodit: settitle, setmodal ja setresizable. hide ja show. Lisäksi metodeja luokilta Component, Container ja Window. Taulukko 5.12: Luokan Dialog konstruktoreita ja metodeja. Kuva 5.7: Köyhän miehen Minefield-peli havainnollistaa dialogeja.

66 66 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA Esimerkki 5.6 Minefield.java dialogien havainnollistus. import java.awt. ; import java.awt.event. ; public class Minefield extends Frame { private boolean[] mines = new boolean[20]; private int solved = 0; private Panel p = new Panel(); public static void main(string[] args) { new Minefield(); Minefield() { super("minefield"); p.setlayout(new GridLayout(5,4)); for (int i=0; i<20; i++) { Button b = new Button(""+(i+1)); p.add(b); b.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { Button b1 = (Button)(e.getSource()); int target = Integer.parseInt(b1.getLabel().trim()); if (mines[target-1]) new GameOverNewTry(Minefield.this,"Sad news...", "Mine! You died! Another one?!?"); else { solved++; b1.setvisible(false); if (solved 10) new GameOverNewTry(Minefield.this, "Great news...", "You did it! Another one?"); ); mines[i] = (Math.random() < 0.15); add(p); addwindowlistener(new IkkunanSulkija()); pack(); setvisible(true); // Minefield() public void restartgame() { for (int i=0; i<20; i++) { mines[i] = (Math.random() < 0.15); p.getcomponent(i).setvisible(true); solved = 0; class GameOverNewTry extends Dialog { GameOverNewTry(Frame f, String s1, String s2) { super(f, s1, true); setsize(150,75); add(new Label(s2), BorderLayout.NORTH); Button b1 = new Button("Yes"); Button b2 = new Button("No"); add(b1, BorderLayout.WEST); add(b2, BorderLayout.EAST); b1.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { restartgame(); setvisible(false); dispose(); ); b2.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { dispose(); System.exit(0); ); pack(); setvisible(true); // // GameOverNewTry // Minefield

67 5.9. TIEDOSTON VALINTAIKKUNA FILEDIALOG 67 Esimerkissä 5.6 luodaan 4 5 nappulaa ja jokaiseen nappulaan liitetään arpomalla tieto onko siinä miina (riippumattomasti, 15% todennäköisyydellä; konstruktorin lopussa). Konstruktorin forsilmukassa nappulat luodaan ja jokaista kohti luodaan anonyymissä sisäluokassa tapahtumankäsittelijä, joka liitetään nappulaan. Käsittelijän merkitys on painnalluksen seurauksena tutkia, oliko nappulan takana miina vai ei ja tarvittaessa nostaa dialogi-ikkuna, joka kertoo miinaan astumisesta. Toisaalta, jos käyttäjä onnistuu avaamaan 10 nappulaa, tapahtumankäsittelijä nostaa dialogin, jossa onnitellaan kentän ratkaisemisesta. Esimerkkiä 5.6 on paikallaan hieman kritisoida. Ensinnäkin main -metodin sisällä oleva anonyymi sisäluokka on varsin suuri olisi selkeämpää toteuttaa se (staattisena) sisäluokkana. Esimerkissä myös luodaan tarpeettoman paljon tapahtumankäsittelijöitä. Jokaiseen nappulaan voitaisiin kiinnittää yksi ja sama tapahtumankäsittelijäolio. Lisäksi, käsittelijän sisällä luodaan onnistumis- ja epäonnistumistilanteissa GameOverNewTry -luokan mukainen dialogi-ikkuna. Jatkettaessa peliä pitkään, syntyy tarpeettomasti dialogi-ikkunoita, jotka käytön jälkeen muuttuvat JVM:n kannalta roskaksi. GameOverNewTry -olioihin luodaan kaksi nappulaa, joihin kumpaankin pitää itse liittää toiminnallisuus tapahtumankäsittelijän muodossa kummankin toteuttaminen anonyyminä sisäluokkana on perusteltua, koska kumpikin on toiminnaltaan lyhyt. Luokan GameOverNewTry konstruktorissa on kuitenkin yksi asia tehty huonosti: konstruktorin lopussa dialogi-ikkunasta tehdään näkyvä. Tätä toimintaa ei kannattaisi sitoa luontioperaatioon. 5.9 Tiedoston valintaikkuna FileDialog Luokalla FileDialog toteutetaan hyvin monipuolinen tiedoston nimen valintatyökalu. Luokka File- Dialog on dialogi, ja se perii luokasta Dialog. Ideana on pyytää käyttäjää valitsemaan tiedoston nimi, kirjoittamista tai lukemista varten. Työkalu osaa navigoida allaolevassa tiedostojärjestelmässä. Työkalua käyttämällä voisi kuvitella, että tiedosto samalla avataan, joko lukemista tai kirjoittamista varten, mutta tällä työkalulla pyritään vain määrittämään tiedoston nimi. FileDialog-olio on modaalinen, eli se valtaa koko huomion. Tämä dialogi on myös komponenttisäiliö (BorderLayout), mutta yleensä tätä käytetään sellaisenaan. Valintatyökalua käsitellään melkein kuin tavallista dialogi-ikkunaakin. Se tuodaan esiin setvisible(true) :lla ja se poistuu, kun valinta on tehty! FileDialog vaatii omistajaoliokseen kehyksen. FileDialog(Frame o) Konstruktori; tyhjä otsikko; omistaja o. Lukeminen! FileDialog(Frame o, String t) Kuten edellinen; otsikko t. FileDialog(Frame o, String t, int m) Kuten edellinen; käyttötarkoitus: m FileDialog.SAVE tai LOAD. Havainnointimetodit: getmode, getfile, getdirectory, getfilenamefilter. Vastaavat modifiointimetodit: setmode, setfile, setdirectory, setfilenamefilter. Lisäksi metodeja luokilta Component, Container, Window ja Dialog. Taulukko 5.13: Luokan FileDialog konstruktoreita ja metodeja. Esimerkillä 5.7 (kuva 5.8) havainnollistetaan tiedoston nimen valitsemista. Pääikkunan nappuloiden klikkaamisen seurauksena syntyy valintatyökaluikkuna, jonka avulla voidaan tiedoston nimi valita. Huomaa, miten valitun tiedoston nimi selvitetään dialogi-oliosta sen näkymättömäksi muuttumisen jälkeen.

68 68 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA Kuva 5.8: Tiedoston nimen valitseminen. Esimerkki 5.7 FileChooser.java tiedoston valitseminen. import java.awt. ; import java.awt.event. ; public class FileChooser { private static TextField tf = new TextField(20); public static void main(string[] args) { Frame f = new Frame("File Chooser"); f.setsize(150,100); Button b1 = new Button("Load"); Button b2 = new Button("Save"); f.setlayout(new GridLayout(0,1)); f.add(b1); f.add(b2); f.add(tf); b1.addactionlistener(new BAction(f, FileDialog.LOAD)); b2.addactionlistener(new BAction(f, FileDialog.SAVE)); tf.seteditable(false); f.setvisible(true); f.addwindowlistener(new IkkunanSulkija()); // main static class BAction implements ActionListener { private FileDialog dialog; private String tila; BAction(Frame f, int m) { tila = (m == FileDialog.SAVE? "Talletus": "Lukeminen"); dialog = new FileDialog(f, tila, m); public void actionperformed(actionevent e) { dialog.setvisible(true); String dir = dialog.getdirectory(); if (dir == null) dir = "(none)"; String tdsto = dialog.getfile(); if (tdsto == null) tdsto = "(none)"; tf.settext(tila + ": " + dir + tdsto); // actionperformed // BAction // FileChooser

69 5.10. MENUPALKKI MENUBAR JA SIIHEN LIITTYVÄT LUOKAT Menupalkki Menubar ja siihen liittyvät luokat Menupalkki on hyvin tuttu monista sovelluksista. Menupalkki koostuu alasvedettävistä menuvalikoista. Kukin menuvalikko puolestaan koostuu alkeisalkioista tai alimenuista. Menuvalikot ovat GUIkomponenttien tapaisia, mutta esiintyvät vain Frame:n osana MenuBar:ssa tai yleensä ponnahdusvalikoissa (popup menus). Menuiden hierarkkisen rakenteen alkeisalkioita ovat leimat, valintalaatikot ja eroittimet (vaakasuuntainen viiva). Menun osiin voidaan liittää tapahtumankäsittelijöitä kuten GUIkomponentteihinkin. Lisäksi AWT:n menuihin liittyy mahdollisuus tehdä valintoja pikanäppäimillä. Menuihin liittyvät ainakin seuraavat AWT:n luokat: MenuBar, MenuComponent Menu, MenuItem, MenuContainer, CheckboxMenuItem, PopupMenu, MenuShortcut. Näiden suhdetta toisiinsa perimysmielessä on havainnollistettu kuvassa 5.9. MenuComponent MenuContainer MenuBar MenuItem CheckboxMenuItem Menu PopupMenu Kuva 5.9: Menuihin liittyvien luokkien hierarkia. Luokka MenuComponent on abstrakti: se määrittelee menualkioiden yleiset ominaisuudet, ja siten se on verrattavissa luokkaan Component. Luokka määrittelee fontin ja nimen käsittelyn sekä AWT-tapahtumankäsittelyn delegointimetodit. Luokka MenuBar on konkreetti luokka ja se määrittelee menupalkin. Sellaisen voi liittää osaksi Frame-oliota. Menupalkki sisältää listan Menu-olioita tavallaan palkki on säiliö. Menupalkkiin suoraan ei kuitenkaan voi liittää tapahtumankäsittelijöitä. Tavallisten menuiden lisäksi menupalkkiin voi liittyä erityinen help-menu. Luokka MenuItem esittää menualkioita, joita ovat leima, menuvalikko ja valintalaatikko. Sellaisenaan MenuItem edustaa leimaa muut toteutetaan aliluokkien CheckboxMenuItem ja Menu kautta. Menualkioon kohdistuu tapahtuma ActionEvent, kun se valitaan. Luokan CheckboxMenuItem avulla esitetään valintalaatikkoa, joka on osana menuvalikkoa. Se sisältönä on leima ja tila. Valittaessa valintalaatikko tuottaa ItemEvent:n. Konstruktorien muoto on tavanomainen ja havainnointi- sekä muutometodit käsittelevät tilaa ja leimaa. Luokan Menu avulla esitetään alasveto(menu)valikkoa. Tavallaan MenuItem vs Menu -suhde on samanlainen kuin Component vs Container -suhde. Sisällöltään menu on pelkkä menualkioiden lista. Luokan MenuShortcut avulla esitetään pikavalintoja. Luokka käyttää KeyEvent:ssä määriteltyjä arvoja merkkien esittämiseen (esim. KeyEvent.VK_A ( A ), KeyEvent.VK_F1 (F1), KeyEvent.VK_AT ( ), KeyEvent.VK_LEFT_PARENTHESIS ( ( ),... ). Painamalla ctrl+merkki, tapah-

70 70 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA MenuBar() Konstruktori, joka luo tyhjän palkin. Menu add(menu m) Lisää menun m palkin loppuun. Menu getmenu(int ind) Palauttaa ind:n menun. void remove(int ind) Poistaa ind:n menun. int getmenucount() Menuiden lukumäärä. Menu gethelpmenu() Palauttaa erityisen help-menun. void sethelpmenu(menu m) Asettaa erityisen help-menun (oikeanpuoleisin). Näitä voi olla vain yksi. Enumeration shortcuts() Palauttaa kokoelman kaikista pikanäppäimistä, jotka liittyvät menupalkin alkioihin. MenuItem getshortcutmenuitem(menushortcut s) Palauttaa sen menualkion, johon liittyy pikanäppäin s. void deleteshortcut(menushortcut s) Poistaa pikanäppäimen s. Taulukko 5.14: Luokan MenuBar konstruktoreita ja metodeja. MenuItem(String n) Tekee menualkion, jolla nimi n. MenuItem(String n, MenuShortcut s) Kuten edellinen, mutta lisäksi pikanäppäin s. Havainnointi ja muuttaminen: getlabel, setlabel, isenabled, setenabled, getshortcut, setshorcut, deleteshortcut, getactioncommand, setactioncommand (oletus on leima). Taulukko 5.15: Luokan MenuItem konstruktoreita ja metodeja. tuu pikavalinta. Esimerkissä 5.8 muodostetaan kaksi tavallista menuuta Frame-olioon liitettävään menupalkkiin. Lisäksi palkkiin liitetään help-menu. Ensimmäinen menuista sisältää joukon ilmeisesti tiedostoihin liittyviä kohtia niihin ei kuitenkaan sovelluksessa ole liitetty muuta toiminnallisuutta kuin sovelluksen lopettaminen. Toinen menu koostuu yhdestä alivalikosta. Kyseisen menun jokaiseen kohtaan on liitetty toiminnallisuus: valinta muuttaa kehyksen taustaväriä. Huomaa, miten tämä toteutetaan luokan MenuKuuntelija avulla. Help-menu havainnollistaa lähinnä erilaisia menualkioita. Huomaa, miten muutamiin menun alkioihin on liitetty pikanäppäin. Menu(String name) Konstruoi menuvalikon, jolla nimi name (menupalkissa). MenuItem add(menuitem mi) Lisää menualkion mi loppuun. void add(string leima) Lisää leima (MenuItem:na) loppuun. void addseparator() Lisää eroittimen, vaakasuoran viivan. Lisäksi havainnointi ja muutosmetodeja: getitem, getitemcount, remove, removeall,... Taulukko 5.16: Luokan Menu konstruktoreita ja metodeja.

71 5.10. MENUPALKKI MENUBAR JA SIIHEN LIITTYVÄT LUOKAT 71 Esimerkki 5.8 Menupalkin muodostaminen. import java.awt. ; import java.awt.event. ; public class MenuDemo { public static void main(string[] args) { Frame f = new Frame("MenuDemo"); f.setsize(400,200); Menu m1 = new Menu("File"); MenuItem openw = new MenuItem("Open", new MenuShortcut(KeyEvent.VK_O)); MenuItem closew = new MenuItem("Close"); MenuItem quitw = new MenuItem("Quit"); m1.add(openw); m1.add(closew); m1.addseparator(); m1.add(quitw); quitw.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { System.exit(0); ); Menu m2 = new Menu("Väri"); MenuItem i1, i2, i3, i4; i1 = new MenuItem("Punainen"); i2 = new MenuItem("Sininen", new MenuShortcut(KeyEvent.VK_S)); i3 = new MenuItem("Keltainen"); i4 = new MenuItem("Vihreä", new MenuShortcut( V )); i1.addactionlistener(new MenuKuuntelija(f, Color.red)); i2.addactionlistener(new MenuKuuntelija(f, Color.blue)); i3.addactionlistener(new MenuKuuntelija(f, Color.yellow)); i4.addactionlistener(new MenuKuuntelija(f, Color.green)); Menu m3 = new Menu("Lisää"); m3.add(i3); m3.add(i4); m2.add(i1); m2.add(i2); m2.addseparator(); m2.add(m3); MenuBar palkki = new MenuBar(); palkki.add(m1); palkki.add(m2); Menu help = new Menu("Apua"); help.add("luokat"); help.add(new MenuItem("Applet")); help.add(new MenuItem("Frame")); help.addseparator(); help.add("jvm"); help.add(new CheckboxMenuItem("Säikeet")); help.add(new MenuItem("Luokkien lataus")); palkki.sethelpmenu(help); f.setmenubar(palkki); f.setvisible(true); // main static class MenuKuuntelija implements ActionListener { private Color color; private Frame frame; public MenuKuuntelija(Frame f, Color c) { frame = f; color = c; public void actionperformed(actionevent e) { frame.setbackground(color); // actionperformed // MenuKuuntelija //

72 72 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA 5.11 Ponnahdusvalikko Luokalla PopupMenu toteutetaan ns. ponnahdusvalikot. Kuten kuvasta 5.9 nähdään, PopupMenu on luokan Menu aliluokka ponnahdusvalikoita voidaan käyttää myös menupalkissa. Ponnahdusvalikko ponnahtaa esiin jonkin tapahtuman laukaisemana: esim. klikattaessa komponettia oikeanpuoleisella hiirinäppäimellä. Vaikka ponnahdusmenun voi liittää mihin tahansa komponenttiin ja matalan tason tapahtumien yhteydessä voidaan selvittää, onko kyseinen tapahtuma sellainen, joka allaolevassa ikkunointijärjestelmässä laukaisee ponnahdusvalikon esiin ponnahtamisen, niin ponnahtaminen pitää tehdä itse tapahtumankäsittelijällä! Lisäksi, kun ponnahdusvalikko nostetaan esiin, niin sen show - metodissa pitää kertoa, minkä komponentin suhteen valikko nostetaan esiin (vaikka se olisi kiinnitetty johonkin komponenttiin). Muutoin PopupMenu on kuin Menu. Esimerkissä 5.9 luodaan ponnahdusvalikko, jonka nostaminen esiin tehdään kiinnittämällä kehysolioon hiirtapahtumien kuuntelija, joka reagoi oikeanpuoleisen hiirinäppäimen painallukseen (huomaa, miten se toteutetaan). Esimerkki 5.9 Popup-menun muodostaminen ja käyttäminen. import java.awt. ; import java.awt.event. ; public class PopupDemo { public static void main(string[] args) { Frame f = new Frame("MenuDemo"); f.setsize(400,200); f.addwindowlistener(new IkkunanSulkija()); PopupMenu popup = new PopupMenu("Väri"); MenuItem i1, i2, i3, i4; i1 = new MenuItem("Punainen"); i3 = new MenuItem("Keltainen"); i2 = new MenuItem("Sininen", new MenuShortcut(KeyEvent.VK_S)); i4 = new MenuItem("Vihreä", new MenuShortcut( V )); i1.addactionlistener(new MenuKuuntelija(f, Color.red)); i2.addactionlistener(new MenuKuuntelija(f, Color.blue)); i3.addactionlistener(new MenuKuuntelija(f, Color.yellow)); i4.addactionlistener(new MenuKuuntelija(f, Color.green)); Menu m3 = new Menu("Lisää"); m3.add(i3); m3.add(i4); popup.add(i1); popup.add(i2); popup.addseparator(); popup.add(m3); f.add(popup); f.addmouselistener(new PopupKuuntelija(f,popup)); f.setvisible(true); // main static class PopupKuuntelija extends MouseAdapter { private Component kohde; private PopupMenu menu; PopupKuuntelija(Component c, PopupMenu p) { kohde = c; menu = p; public void mouseclicked(mouseevent e) { if ((e.getmodifiers() & InputEvent.BUTTON3_MASK) 0) menu.show(kohde, 20, 20); / Tai toisin... public void mousepressed(mouseevent e) { if (e.ispopuptrigger()) menu.show(kohde, 20, 20); / // PopupKuuntelija static class MenuKuuntelija implements ActionListener { private Color color; private Frame frame; public MenuKuuntelija(Frame f, Color c) { frame = f; color = c; public void actionperformed(actionevent e) { frame.setbackground(color); // MenuKuuntelija // PopupDemo

73 Luku 6 GUI-sovellusten rakenteesta Edellisissä luvuissa on käyty läpi tapahtumankäsittely ja AWT:n komponentit. Tässä luvussa pohditaan, miten niiden avulla tulisi rakentaa GUI-sovelluksia. Luvussa 6.2 pyritään pilkkomaan GUIsovelluksen muodostaminen hallittavan kokoisiin vaiheisiin. Kehittäminen perustuu ajatukselle, että GUI-sovellusten voidaan nähdä toimivan tila-automaattien tapaan siten, että automaatin transitioita vastaavat tapahtumankäsittelijät! Tästä lisää seuraavaksi luvussa 6.1. Tämän luvun tarkoitus on näyttää käytännössä, miten rakentaa GUI-sovellus. Sitä demonstroidaan tekemällä luvussa 6.4 MasterMind-peli, jolla on graafinen käyttöliittymä 1. Peliä voi käyttää sekä GUIsovelluksena että applettina. Ennen pelin muodostamista, luvussa 6.3 tarkastellaan, miten kannattaisi tehdä sovellus, joka toimii samanaikaisesti sekä GUI-sovelluksena että sovelmana. MasterMind on kahden pelattava lautapeli. Pelin idea on, että ensimmäinen pelaaja (jatkossa arvuuttelija) ensin asettaa neljään kohtaan valitsemansa väriset nappulat, minkä jälkeen toinen pelaaja (jatkossa pelaaja) yrittää selvittää arvuuttelijan kätkemien nappuloiden värit ja järjestyksen. Kun pelaaja yhden pelivuoron aikana ehdottaa ratkaisuaan ohjelman valitsemien nappuloiden väreiksi, arvuuttelija muodostaa pelaajalle vastauksen, joka kertoo, miten hyvä arvaus oli. Vastauksessa käytetään kahdenvärisiä nappuloita, mustia ja valkoisia. Yksi musta tarkoittaa, että yksi oikean värinen nappula on ollut oikeassa paikassa. Kaksi mustaa tarkoittaa, että arvauksessa on kaksi nappulaa oikeilla paikoilla. Neljä mustaa nappulaa puolestaan tarkoittaa, että pelaaja on ratkaissut arvuuttelijan arvoituksen. Valkoinen puolestaan tarkoittaa, että ehdotuksessa on oikean värinen nappula, mutta se on väärässä paikassa. Sekä valkoinen että musta kuluttavat yhden nappulan sekä ehdotuksesta että oikeasta ratkaisusta. Yleensä pelaaja saa tehdä 6 arvausta. Tässä luvussa toteutettavassa pelissä sekä arvuuttelijan värien, pelaajalle sallittavien arvausten että arvuuteltavien nappuloiden lukumäärät voivat vaihdella tietyissä rajoissa. 6.1 GUI-sovellus tilakoneena Tähän mennessä on opittu, että GUI-sovellukset rakennetaan muodostamalla alustusvaiheen seurauksena sovellus, johon on aseteltu GUI-komponentit halutulla tavalla ja sovelluksen toiminnallisuus on muodostettu liittämällä GUI-komponentteihin sopivalla toiminnallisuudella varustettuja tapahtumankäsittelijöitä. Sovellusta voidaan helposti ajatella automaattina, jolla on kullakin hetkellä tila. Tila on sama kuin sovelluksen tietosisällön arvo kullakin hetkellä tietosisältö tarkoittaa kaikkia niitä olioita ja arvoja, joihin sovelluksesta päästään käsiksi. Tapahtumien seurauksena sovelluksen tila muuttuu 1 Ohjelmointi I kurssin materiaalin puitteissa on rakennettiin samainen peli tekstipohjaisella käyttöliittymällä. Nyt tehtävä peli on paljon monipuolisempi. 73

74 74 LUKU 6. GUI-SOVELLUSTEN RAKENTEESTA esimerkiksi, esimerkissä 4.1 tapahtumankäsittelijöiden avulla muutettiin tekstikentässä esitettävän laskurin arvoa. Yleisesti voitaisiin ajatella, että GUI-sovelluksella on hyvin suuri määrä erilaisia tiloja (kaikki erilaiset lailliset ja mahdolliset tietosisällön arvot). Tapahtumankäsittelijät määrittelevät tällaisen äärellisen automaatin tilojen väliset transitiot, eli tilasiirtymät. Tämä ajattelutapa on sikäli ongelmallinen, että tiloja on todella paljon ja yksi tapahtumankäsittelijä implisiittisesti määrittelee hyvin monta transitiota. Toinen lähestymistapa on mallintaa tilat metatiloina tai jopa metatilojen (osatilojen) tulona. Metatilalla tarkoitetaan tässä yhteydessä alkeistilojen joukkoa, johon liittyy sama joukko aktiivisia tapahtumankäsittelijöitä ja ärsykkeiden (tapahtumien) tuottamismahdollisuuksia. Esimerkiksi, jos jonkin sovelluksen suorituksen aikana nostetaan esiin modaalinen dialogi-ikkuna, sovelluksen voidaan katsoa siirtyvän metatilasta toiseen, sillä alkuperäisen ikkunan tapahtumankäsittelijät eivät ole käytettävissä niin kauan kuin modaalinen dialogi-ikkuna on esillä. Metatilalla pyritään tässä siis ilmaisemaan sovelluksen tilaa, johon liittyy tietynlainen toiminnallisuuden mahdollisuus. Metatilojen tulon ajatus liittyy siihen, että iso sovellus voi koostua osista, jotka vaikuttavat toisiinsa hyvin vähän (oikeastaan pitäisi vaatia, että osien välistä vuorovaikutusta ei ole lainkaan). Kutakin osaa voidaan pitää yhtenä (meta)osatilana ja kokonaisuutta osatilojen tulona. Tuolloin sovelluksen metatiloja voidaan tarkastella osakohtaisesti kokonaisuuden projektioina, mikä tekee suunnittelusta, toteutuksesta, testauksesta ja dokumentoinnista modulaarisempaa (eli helpommin hallittavaa). Suunnitteluvaiheessa GUI-sovellus kannattaa yrittää nähdä joukkona metatiloja ja niiden välisiä tilasiirtymiä. Tämä helpottaa sovelluksen suunnittelua huomattavasti, sillä metatilat usein muodostuvat täysin erillisistä GUI-komponenteista ja niihin liitetyistä tapahtumankäsittelijöistä. Metatilat usein samaistuvat toteutusvaiheessa päätason ikkunoihin (kehyksiin, dialogeihin). Lisäksi metatiloihin liittyvät transitiot ovat tapahtumankäsittelijöitä, joita varten sovelluksessa pitää esitellä luokkia. Eli, metatila-ajattelu hyvin usein paljastaa suuren määrän sovellukseen muodostuvista luokista. 6.2 Miten rakentaa GUI-sovellus? GUI-sovellusten rakentamisesta voidaan antaa paljon neuvoja koskien käyttöliittymän tyylikkyyttä, käytettävyyttä ja vaikkapa psykologiaan perustuvaa vaikuttavuutta ja miellyttävyyttä. Edellisten adjektiivien hyvyys on valitettavasti usein kovin subjektiivista, mutta monia yleisiäkin asioita on todettavissa. Tässä luvussa pyritään kuitenkin vain antamaan teknisiä neuvoja koskien kysymystä, miten rakentaa GUI-sovellus. Seuraavassa oletetaan, että on selvää, millainen sovellus halutaan tehdä ja mitä ominaisuuksia sillä halutaan olevan. Rakentamisen pohtiminen voidaan aloittaa vaikkapa kysymällä, mistä GUI-sovellus teknisessä mielessä koostuu? Selvästikin osat ovat seuraavat. 1. Graafiset komponentit. 2. Tapahtumankäsittelijät. 3. Tietorakenteet ylläpidettävälle tiedolle. GUI-komponentteihin liitettyjen tapahtumankäsittelijöiden avulla halutaan vaikuttaa johonkin ja tämä jotakin on sovelluksen tietosisältö, jota varten ovat omat tietorakenteensa. Yksinkertaisimmillaan tietorakenne on joukko perustyyppien mukaisia luokka- ja/tai instanssimuuttujia. Toisaalta hyvinkin monimutkaisia tietorakenteita saatetaan käyttää ja osaa tiedoista voidaan ylläpitää tiedostoissa, tietokannoissa tai esimerkiksi verkon kautta käytettävissä tietovarastoissa.

75 6.2. MITEN RAKENTAA GUI-SOVELLUS? 75 Luettelon kolme kohtaa muodostavat tunnetun kolminaisuuden: Model-View-Controller, eli MVC. Itse asiassa MVC on yleinen suunnittelumalli. Ideana on erottaa selkeästi toisistaan systeemin tila (model), graafisesta ulkoasusta (view) ja sitä hallitsevasta toiminnallisuudesta (controller). Javan AWT-pohjaisten GUI-sovellusten tapauksessa toiminnallisuus on joukko tapahtumankäsittelijöitä 2. Vastaavasti view on sovelluksen ulkoasu. Malli (model) on erillinen osa (joukko luokkia), joiden tehtävänä on esittää tilaa ja mahdollistaa erilaiset lailliset tilan muutokset. View :n tehtävänä on esittää kulloinenkin ulkoasu vastaten mallin tilaa. Controller :n tehtävä on mahdollistaa tilan muutokset ja tarvittaessa huolehtia siitä, että view on ajan tasalla. MVC ei varsinaisesti ota kantaa siihen, mitä kautta muutokset tilaan tehdään ja miten view :n ja model :n ajantasaisuus taataan. Tästä johtuen MVC:n väliset vuorovaikutukset esitetään kuvan 6.1 mukaisesti ottamalla kantaa ajantasaisuuden tekemiseen, osa kaarista voidaan poistaa. Controller Model View Kuva 6.1: MVC:n osat. MVC-ajattelun perusteella luokkia syntyy tapahtumankäsittelijöistä, sovelluksen tilan esittämisestä ja sovelluksen tilan yhdistämisestä ulkoasuun. Viimeisin tarkoittaa luokkia, joilla esitetään (tai muodostetaan) GUI-komponenttien sommiteltuja kokoelmia siten, että niihin liittyy haluttu toiminnallisuus tapahtumankäsittelijöiden muodossa. MVC:n osien muodostamisen kannalta olennaisia kysymyksiä ovat miten uudelleenkäytettäviä osat ovat? tulisiko GUI-komponenttien kokonaisuudet muodostaa asiakas- vai perintäsuhteen avulla (Frame:n käyttö)? Mallin tulee olla GUI-osuudesta riippumaton, joten se on usein uudelleenkäytettävissä. Usein GUIkokonaisuudet rakentuvat ikkunoiden ympärille jos jokin ikkuna on yleiskäyttöinen, siitä kannattaa perimällä muodostaa luokka. Muutoin asiakas- ja perimyssuhteen välinen kysymys on lähinnä tekninen ja ohjelmointityylikysymys. Kokonaisuuden hallitsemiseksi osa luokista usein kannattaa toteuttaa sisäluokkina: jopa malli voitaisiin ajatella toteuttaa sisäluokkana, joskin yleensä lähinnä tapahtumankäsittelijät toteutetaan sisäluokkina. 2 Tilannetta on tosin mahdollista rikastuttaa ottamalla käyttöön useita säikeitä, esimerkiksi animoinnin toteuttamiseksi.

76 76 LUKU 6. GUI-SOVELLUSTEN RAKENTEESTA Edellä MVC:n yhteydessä ajateltiin, että käyttöliittymän käyttäjä on ainoa taho, joka vaikuttaa controller -osan kautta. Tilanne voi kuitenkin olla monimutkaisempi: useita säikeitä ja toisia sovelluksia verkkoyhteyksien 3 kautta. 6.3 Appletti ja sovellus yhdessä Luokka voi toimia samanaikaisesti sekä sovelluksena ja sovelmana (applettina). Tuolloin luokalla on sekä main -metodi että se perii luokasta Applet (suoraan tai epäsuorasti). On varsin kätevää, jos ohjelmaa voidaan vaihtoehtoisesti suorittaa GUI-sovelluksena tai applettina. Seuraavassa pyritään muodostamaan perusteltu resepti eri osien rakentamiseksi tällaisessa tilanteessa. Yksinkertaisissa tapauksissa sovelluksen komponentit muodostetaan main -metodissa kun taas appleteilla vastaava metodi on init, jota kutsutaan kerran appletin luonnin jälkeen. Ilmeisestikin kannattaa muodostaa yhteinen alustusosuus, jossa luodaan GUI-komponentit, ja jota kutsutaan sekä main :sta että init :stä. Yksi perusongelma kuitenkin on: appletti on komponettisäiliö, mutta main - metodi on staattinen eikä taustalla ole kutsuhetkellä oliota. Ratkaisu tähän ongelmaan on, että appletti on perimyssuhteiden takia myös Panel-tyyppinen komponenttisäiliö, jolloin se voidaan sijoittaa suoraan osaksi kehystä (Frame). Toinen tapa ratkaista ongelma on, että luodaan yhteisessä alustusosassa Panel-tyyppinen komponenttisäiliö, joka sijoitetaan init :n tapauksessa Applet-olioon ja main :n tapauksessa Frame-olioon. Yhteisen alustusosan muodostamisen kannalta on vielä toinenkin ongelma: tiedonvälitystavat ovat erilaisia. Metodissa init voidaan tietoa hakea HTML-sivulta välitetyistä parametreista kun taas main :n tapauksessa tietoa välitetään String[]-tyyppisen taulukon kautta. Kummassakin tapauksessa välitettävät tiedot pitää siis etsiä esiin ja välittää yhteiselle osalle. Vielä on eräs pienehkö ongelma: dialogi-ikkunat nimittäin vaativat omistajaksi kehysolion. Tämä on ongelma init -metodin kannalta. Se voidaan ratkaista luomalla näkymätön dummy -kehysolio, joka sitten välitetään alustusosalle, jossa dialogi-ikkunat luodaan. Tultaessa sovellukseen main - metodin kautta tilanne on suoraviivaisempi: yhteiselle alustusosalle voidaan välittää pääikkunaa esittävä kehysolio. Mikä sitten on yhteinen osa? Saattaisi houkutella muodostaa yhteiseksi osaksi appletin konstruktori, mutta se ei ole toimiva ratkaisu, sillä appletin konstruktorilla ei ole käytettävissään tietoa appletin ympäristöstä (HTML-tiedostosta välitettävät parametrit). Tällainen yhteysolio liitetään applettiin selaimen toimesta vasta välittömästi luonnin jälkeen (ja ennen init :n kutsumista). Yhteinen osuus on siis jokin tavallinen (itsetehtävä) metodi. Yhteistoimintaan liittyy vielä yksi ongelma: miten lopettaa suoritus? Sovelluksen tapauksessa tämä voi tapahtua kutsumalla jossakin tapahtumankäsittelijässä System.exit(0);, jonka seurauksena kaikki sovelluksen säikeet kuolevat. Applettien kohdalla tilanne on vaikeampi: applettia nimittäin suoritetaan esimerkiksi selaimeen upotetussa virtuaalikoneessa ja kyseinen virtuaalikone voi suorittaa samanaikaisesti useita muita appletteja. Itse asiassa päätös sovelman suorituksen lopettamisesta kuuluu pikemminkin selaimelle kuin sovelmalle itselleen. Mahdollisia tapoja lopettaa appletti sen itsensä toimesta ovat kaikkien tapahtumankäsittelijöiden poisto tai kaikkien komponenttien passivointi. Edellinen kuitenkin tarkoittaa, että lopettaminen pitää suorittaa tilanteesta riippuen eri tavalla ja lopettaminen vaatii tietoa siitä suoritetaanko luokkaa applettina vai sovelluksena. Kyseinen tieto voidaan luoda helposti init - ja main -metodeissa. 3 Asiakas-palvelin sovelluksissa GUI-sovellukseen liittyvä malli on usein suurelta osin palvelimessa. Esimerkiksi, jos palvelin on pelipöytä ja asiakas on pelaaja, niin pelin tilaa kuuluu ylläpitää palvelimessa.

77 6.4. ESIMERKKISOVELLUS MASTERMIND Esimerkkisovellus MasterMind Miten tehdä (tämän luvun alussa kuvattu) MasterMind-peli? Haluttaisiin saada aikaan luokkia, joiden avulla MasterMind:a voitaisiin suorittaa sovelluksena (kuva 6.2) ja sovelmana (kuva 6.3). Kummassakin kuvassa yksi rivi koostuu pelaajan tekemistä valinnoista (neljä nappulaa vasemmassa reunassa); nappulasta, jolla ilmoitetaan värivalinnat tehdyiksi; sekä valinnan seurauksena arvuuttelijan vastineesta (enintään neljä mustaa ja/tai valkoista nappulaa oikeassa reunassa). Rivien määrä kertoo montako arvausta pelaajalla on. Oikea vastaus on kummassakin kuvassa ratkaisemisen jälkeen paljastettu pääruudun alareunassa. Peli päättyy joko onnistumiseen tai epäonnistumiseen: oikeat värit löytyvät tai kaikki arvausrivit käytetään. Kummassakin tapauksessa tilanteesta informoidaan pelaajaa dialogi-ikkunan avulla. Kuva 6.2: MasterMind-peli sovelluksena. Sovelluksen toiminta pitäisi nyt pääpiirteissään olla kiinnitetty, mutta miten se toteutetaan? Koska toiminnallisuus on tiedossa, voitaisiin (1) lähtökohdaksi valita mallin, eli sovelluksen logiikan, toteuttaminen täysin erillään GUI-osuudesta. Mallin muodostamisen jälkeen pitää pohtia, miten (2) graafisuus toteutetaan GUI-komponenttien avulla ja (3) miten komponentit kootaan kokonaisuuksiksi. Käytännössä kukin ikkuna muodostaa yhden kokonaisuuden. Tämän jälkeen voitaisiin pohtia, (4) mitä toimintoja GUI-komponentteihin tulisi liittyä ja (5) millaisen metamallin sovellus muodostaa. Tässä vaiheessa pitäisi olla jo tarpeeksi informaatiota (6) määritellä ja lopuksi toteuttaa itse sovelluksen toteuttavat luokat. Kuvassa 6.4 on muodostettu malli MasterMind-pelin metatiloista ja niiden välisistä transitioista.

78 78 LUKU 6. GUI-SOVELLUSTEN RAKENTEESTA Kuva 6.3: MasterMind-peli applettina. Transitiot eivät ole vain suoraviivaisia siirtymiä tilasta toiseen, vaan mahdollisesti ehdollisia, jolloin siirtymän kohde riippuu sovelluksen tilasta. Kaikki pyöreäreunaisista laatikoista (kuvaavat metatilaarvoja) lähtevät nuolet ovat tapahtumankäsittelijöitä. Katkoviivalla rajattu osuus muodostaa yhden tapahtumankäsittelijän toiminnallisuuden se on suurehko, koska sen toiminta riippuu ehdollisesti sovelluksen tilasta. MasterMind-pelin tapauksessa metatilat esittävät pääasiallisesti komponenttisäiliöitä niihin liittyvä toteutus on merkitty laatikon ulkopuolelle oikeaan yläreunaan. MasterMind-pelin malli ylläpitää tietoa rivikohtaisesti tehdyistä värivalinnoista sekä myös arvuuteltavasta rivistä. Värivalintojen tekemisen jälkeen mallin tehtävänä on laskea, montako mustaa ja valkoista nappulaa tehty värivalinta implikoi. Esimerkissä 6.1 esitettävä mallin toteuttava koodi on tehty yleiseksi rivien, pelaajan nappuloiden värien lukumäärän sekä arvuuteltavien nappuloiden lukumäärän suhteen. Tieto näiden arvoista on myös osa mallin tietosisältöä. Mallissa on tehty yksi pelin tilan esittämistä koskeva kiinnitys: värejä esitetään numeroilla 1... colors. Konstruktoreilla tietosisältö alustetaan arvuuteltava rivi arvotaan samalla. Huomaa, että mallin

79 6.4. ESIMERKKISOVELLUS MASTERMIND 79 main alustus init valitaan värejä (perustila) aseta väri väri valittu PopupMenu värin asetus - resetoi peli värit asetettu kaikki asetettu? ei ok Dialog Virhetila: osa harmaita - resetoi peli uudestaan Dialog kyllä - lisää rivi malliin - mustia? - valkoisia? lopeta Game Over Solved! kyllä ratkaistu? ei - generoi uusi rivi - estä edellisen rivin toiminta uudestaan lopeta Dialog Game Over Failed! ei jatkettavissa? kyllä tapahtuman käsittelijä! Kuva 6.4: MasterMind-pelin tilojen metamalli. tehtävänä on esittää pelin tilaa, ei pelata sitä. Niinpä arvuuteltavan rivin asettaminen ja havainnoiminen tulee mahdollistaa. Malli tarjoaa luonnollisesti metodin setnextguess uuden rivin kiinnittämiseksi sekä suurehkon joukon metodeja mallin eli pelin tilan havainnoimiseksi. Huomaa, miten arvuuteltavan rivin asettamisen yhteydessä tulee myös nollata arvatut rivit. Malli on toteuttu niin, että uusia arvausrivejä voi lisätä senkin jälkeen kun oikea rivi on jo löytynyt. Tämä kuitenkin huomioidaan metodissa issolved.

80 80 LUKU 6. GUI-SOVELLUSTEN RAKENTEESTA Esimerkki 6.1 MasterMindModel.java pelin malli. public class MasterMindModel { // Luokka esittää MasterMind-pelin tilaa public final static int ROWMAX = 10; public final static int ROWMIN = 3; public final static int PERROWMAX = 6; public final static int PERROWMIN = 3; public final static int COLORMAX = 8; public final static int COLORMIN = 3; private int rows; // rivien lukumäärä private int perrow;// nappuloita per rivi private int colors;// erilaisia värejä nappuloilla // Värejä esitetään numeroilla 1..COLORMAX private int[] goal = new int[perrowmax]; private int rowtable[][]; private int currentrow; private int responsetable[][]; // rows x 2; [..][0] = #black; [..][1] = #white public MasterMindModel() throws Exception { this(8,4,6); public MasterMindModel(int r, int n, int c) throws Exception { // r: rivien lukumäärä; n: nappuloita per rivi; c: värien lukumäärä // Jos r, n ja v laittomia => Exception if (ROWMIN > r ROWMAX < r) throw new Exception("MasterMind: Illegal number of rows."); if (PERROWMIN > n PERROWMAX < n) throw new Exception("MasterMind: Illegal number of pieces per row."); if (COLORMIN > c COLORMAX < c) throw new Exception("MasterMind: Illegal number of rows."); rows = r; perrow = n; colors = c; rowtable = new int[rows][]; responsetable = new int[rows][2]; currentrow = -1; setrandomgoal(); //MasterMindModel(r,n,c) public void setgoal(int[] g) throws Exception { if (g == null) throw new Exception("Integer table expected."); if (g.length perrow) throw new Exception("Expecting " + perrow + "color specifications per row."); for (int i=0; i< perrow; i++) { if (g[i] < 0 g[i] colors) throw new Exception("Illegal color code " + g[i] + "at position " +i); goal[i] = g[i]; currentrow = -1; // setgoal public void setrandomgoal() { for (int i=0; i<perrow; i++) goal[i] = (int)(math.random() colors); currentrow = -1; // setrandomgoal... jatkuu...

81 6.4. ESIMERKKISOVELLUS MASTERMIND 81 Esimerkki 6.1. MasterMindModel.java pelin malli (jatkoa).... jatkoa... public void setnextguess(int[] g) throws Exception { if (!canstillguess()) throw new Exception("All guesses used!"); if (g == null) throw new Exception("Integer table expected."); if (g.length perrow) throw new Exception("Expecting " + perrow + "color specifications per row."); rowtable[currentrow+1] = new int[colors]; for (int i=0; i< perrow; i++) { if (g[i] < 0 g[i] colors) throw new Exception("Illegal color code " + g[i] + "at position " +i); rowtable[currentrow+1][i] = g[i]; // Colors successfully read! currentrow++; responsetable[currentrow][0] = howmanyblack(goal, g); responsetable[currentrow][1] = howmanywhite(goal, g); // setnextguess private int howmanyblack(int[] hidden, int[] user) { // Montako väriä on täsmälleen oikeassa paikassa? int correct = 0; for (int i=0; i<perrow; i++) if (hidden[i] == user[i]) correct++; return correct; // howmanyblack private int howmanywhite(int[] hidden, int[] user) { // Montako väriä oikein, mutta väärässä paikassa? int correct = 0; boolean[] h_usable = new boolean[perrow]; boolean[] u_usable = new boolean[perrow]; for (int i=0; i<perrow; i++) h_usable[i] = u_usable[i] = (hidden[i] user[i]); for (int i=0; i<perrow; i++) if (h_usable[i]) for (int j=0; j<perrow; j++) if (u_usable[j] && (hidden[i] == user[j])) { u_usable[j] = h_usable[i] = false; correct++; break; return correct; // howmanywhite public boolean canstillguess() { return rows-1-currentrow > 0; public int getrows() { return rows; public int getpiecesperrow() { return perrow; public int getcolors() { return colors; public int getnumberofblack() { return getnumberofblack(currentrow); public int getnumberofwhite() { return getnumberofwhite(currentrow); public int getnumberofblack(int r) { return (((0 r) && (r < rows))? responsetable[r][0] : 0); public int getnumberofwhite(int r) { return (((0 r) && (r < rows))? responsetable[r][1] : 0); public boolean issolved() { for (int i=0; i<currentrow; i++) { if (responsetable[i][0] == perrow) return true; return false; // issolved public int[] getgoal() { int[] q = new int[perrow]; for (int i=0; i<perrow; i++) q[i] = goal[i]; return q; // getgoal // MasterMindModel

82 82 LUKU 6. GUI-SOVELLUSTEN RAKENTEESTA Seuraavalla kahdella sivulla on esitetty MasterMind-pelin toteutus (yli 350 riviä ohjelmakoodia). MasterMind-luokka toimii sekä sovelluksena että sovelmana. Metodissa init yritetään lukea HTMLtiedostosta välitettyjen parametrien arvot (muodostaen niistä kokonaislukuja); merkitään talteen (sovelluksen lopettamista varten), että ollaan suorittamassa applettia; luodaan dummy -kehys dialogeja varten; ja lopuksi kutsutaan yhteistä alustusosuutta, metodia initialize. Metodissa main toimitaan samaan tapaan. Luodaan ensin applettiolio; luetaan välitetyt parametriarvot; luodaan kehysolio asettaen applettiolio kehykseen (toimii, koska se on samalla Panel); alustetaan applettiolio initialize :lla; ja tehdään kehysoliosta näkyvä. Yhteisessä alustusosassa initialize luodaan ensin malli käyttäen välitettyjä arvoja, jos ne vain ovat laillisia. Tämän jälkeen luodaan pääikkunan kaikki komponentit jokaiselle riville luodaan kaikki nappulat ja itse asiassa ratkaisukin laitetaan jo valmiiksi pääikkunan alareunaan. Näistä GUIkomponenteista suurin osa laitetaan kuitenkin näkymättömiksi. Ainoastaan ensimmäisen rivin vasemmassa reunassa olevat nappulat sekä keskellä oleva nappula tehdään näkyviksi. Metodin initialize ensimmäisessä for-silmukassa rivit ketjutetaan ( gr.setnext(gr1); ), jotta seuraavalle riville siirtyminen osataan tehdä tapahtumankäsittelijöillä. Metodin initialize loppuosassa luodaan vielä kolme dialogiikkunaa. Muuttujaan allcolorsnotsetdialog luodaan dialogi, jolla kerrotaan, että keskellä olevaa nappulaa painettaessa, kaikkien vasemman puoleisten arvausta edustavien nappuloiden värejä ei oltu vielä asetettu. Huomaa, miten dialogi-ikkunaan luodaan ok -nappula ja siihen kiinnitetään tapahtumankäsittelijä (joka poistaa dialogin). Metodin kahdella viimeisellä rivillä luodaan vielä dialogiikkunat pelin ratkaisemisesta ja ratkaisemisessa epäonnistumisesta tiedoittamista varten. Värillisiä ruutuja jotka edustavat pelin nappuloita on pelissä varsin paljon, ja niinpä niitä varten on toteutuksessa päätetty tehdä oma luokka ColoredButton. Nappulan ominaisuus on taustan väri ilman tekstisisältöä. Lisäksi nappula voi olla aktiivinen tai passiivinen. Aktiivisuus tarkoittaa, että nappulaan on kiinnitetty tapahtumankäsittelijä (metodilla setactive ) ja kyseistä käsittelijää ei ole estetty. Passivointi tarkoittaa koko nappulan toiminnan estämistä, samalla estäen käsittelijän toiminta. Nappulan toiminta voidaan myös aktivoida uudelleen. Taustaväriä voi vaihtaa ja havainnoida. Edellisiä metodeja tarvitaan, koska peli on mahdollista aloittaa uudelleen! Yhden rivin esittämistä ja koordinointia varten on myös tehty oma (sisä)luokka GameRow. Sillä on lähinnä kaksi metodia: konstruktori ja rivin resetointi. Konstruktori luo rivin nappulat aktivoiden vasemmalla puolella olevat nappulat oikealla puolella oleviin ei kiinnitetä tapahtumankäsittelijöitä. Suurimman osan konstruktorista muodostaa rivin keskellä olevaan nappulaan kiinnitetty tapahtumankäsittelijän koodi. Tämä käsittelijä on kuvassa 6.4 katkoviivalla ympäröity tapahtumankäsittelijä. Kuten käsittelijän laajuudesta voidaan päätellä, ohjelmassa on tehty virheellinen suunnittelupäätös, kun kyseinen käsittelijä on toteutettu anonyyminä sisäluokkana. Ohjelman loppuosassa toteutetaan vielä luokat värinvalitsemisen toteuttamiseksi (popup-menu ColorChooser ), edelliseen liittyvä tapahtumankäsittelijä ColorChoiceListener ja pelin päättymisestä kertovien modaalisten dialogien luokka GameOverDialog. Huomaa, miten dialogiin asetettuihin nappuloihin kiinnitetään tapahtumankäsittelijät. Molemmat toteutetaan anonyymeinä sisäluokkina: ensimmäinen lopettaa pelin (huomioiden, miten sovellukseen on tultu) ja toinen resetoi pelin aloittaen samalla uuden pelin.

83 6.4. ESIMERKKISOVELLUS MASTERMIND 83 import java.awt. ; import java.awt.event. ; import java.applet. ; public class MasterMindGame extends Applet { private static Color[] COLORS = { Color.red, Color.green, Color.yellow, Color.blue, Color.orange, Color.cyan, Color.magenta, Color.pink ; private static String[] COLORNAMES = { "red", "green", "yellow", "blue", "orange", "cyan", "magenta", "pink" ; private int colors = 6; private int rows = 8; private int perrow = 4; private MasterMindModel game; private int gameround; private boolean isapplet; private Dialog allcolorsnotsetdialog; private Dialog gameoversolveddialog; private Dialog gameoverunsolveddialog; private GameRow[] gamerows; private ColoredButton[] solution; public void init() { int c, r, p; c = r = p = 0; try { c = Integer.parseInt(getParameter("Colors").trim()); catch (Exception e) { try { r = Integer.parseInt(getParameter("Rows").trim()); catch (Exception e) { try { p = Integer.parseInt(getParameter("PerRow").trim()); catch (Exception e) { isapplet = true; Frame tmpframe = new Frame(); tmpframe.setsize(1,1); tmpframe.setvisible(false); initialize(tmpframe,r,p,c); public static void main(string[] args) { MasterMindGame g = new MasterMindGame(); int c, r, p; c = r = p = 0; try { if (args.length > 0) c = Integer.parseInt(args[0].trim()); catch (Exception e) { try { if (args.length > 1) r = Integer.parseInt(args[1].trim()); catch (Exception e) { try { if (args.length > 2) p = Integer.parseInt(args[2].trim()); catch (Exception e) { Frame f = new Frame("MasterMind"); g.isapplet = false; g.initialize(f,r,p,c); f.add(g); f.pack(); f.addwindowlistener(new IkkunanSulkija()); f.setvisible(true); // main private void resetgame() { game.setrandomgoal(); gameround = 1; for (int i=0; i<rows; i++) { gamerows[i].reset(); gamerows[i].setvisible(false); gamerows[0].setvisible(true); int[] sol = game.getgoal(); for (int i=0; i<perrow; i++) { solution[i].setvisible(false); solution[i].setcolor(int2color(sol[i])); // resetgame private int color2int(color c) { for (int i=0; i<colors.length; i++) if (c == COLORS[i]) return i; return 0; // Should be uncessary... // color2int private Color int2color(int c) { return COLORS[c]; // int2color private void initialize(frame f, int r, int p, int c) { if (c MasterMindModel.COLORMIN && c MasterMindModel.COLORMAX) colors = c; if (r MasterMindModel.ROWMIN && r MasterMindModel.ROWMAX) rows = r; if (p MasterMindModel.PERROWMIN && p MasterMindModel.PERROWMAX) perrow = p; // Create visual components try { game = new MasterMindModel(rows, perrow, colors); catch (Exception e) { // Impossible. gameround = 1; setlayout(new BorderLayout()); Panel p1 = new Panel(); p1.setsize((perrow+1) 20,(rows+1) 20); p1.setlayout(new GridLayout(0,1,5,5)); gamerows = new GameRow[rows]; GameRow gr = new GameRow(); p1.add(gr); gr.setvisible(true); gamerows[0] = gr; for (int i=1; i<rows; i++) { GameRow gr1 = new GameRow(); gr1.setvisible(false); p1.add(gr1); gr.setnext(gr1); gr = gr1; gamerows[i] = gr; p1.setvisible(true); add(p1, BorderLayout.CENTER); // Add solution to the panel solution = new ColoredButton[perRow]; Panel p2 = new Panel(); p2.setlayout(new GridLayout(1,0,5,5)); int[] sol = game.getgoal(); for (int i=0; i<perrow; i++) { ColoredButton cb = new ColoredButton(int2color(sol[i])); solution[i] = cb; cb.setvisible(false); p2.add(cb); p2.setvisible(true); add(p2, BorderLayout.SOUTH); // allcolorsnotsetdialog allcolorsnotsetdialog = new Dialog(f,"Error",true); allcolorsnotsetdialog.setsize(250,80); allcolorsnotsetdialog.setlocation(100,100); allcolorsnotsetdialog.setlayout(new BorderLayout()); allcolorsnotsetdialog.add(new Label("All colors not set!"), BorderLayout.CENTER); Button ok = new Button("Ok"); ok.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { allcolorsnotsetdialog.setvisible(false); ); allcolorsnotsetdialog.add(ok, BorderLayout.SOUTH); // gameoverdialog gameoversolveddialog = new GameOverDialog(f,true); gameoverunsolveddialog = new GameOverDialog(f,false); // initialize public class ColoredButton extends Button { private PopupMenu popup; public ColoredButton() { this(color.gray); public ColoredButton(Color c) { super(); setbackground(c); // ColoredButton() public void setactive() { popup = new ColorChooser(this); add(popup); addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { popup.show(coloredbutton.this, 5, 5); ); // setactive public void setpassive() { this.setenabled(false); public void reactivate() { this.setenabled(true); public Color getcolor() { return (getbackground()); public void setcolor(color c) { setbackground(c); public boolean iscolorset() { return (getbackground() Color.gray); // iscolorset // class ColoredButton

84 84 LUKU 6. GUI-SOVELLUSTEN RAKENTEESTA public class GameRow extends Panel { private GameRow next; private ColoredButton[] result; private ColoredButton[] buttons; private Button allsetb; private Panel left,right; public GameRow() { setlayout(new GridLayout(1,0,5,5)); left = new Panel(); left.setlayout(new GridLayout(1,0)); buttons = new ColoredButton[perRow]; for (int j=0; j<perrow; j++) { ColoredButton cb = new ColoredButton(); cb.setactive(); left.add(cb); buttons[j] = cb; left.setvisible(true); add(left); allsetb = new Button("Colors set"); allsetb.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { int[] row = new int[perrow]; for (int i=0; i<perrow; i++) { if (!buttons[i].iscolorset()) { allcolorsnotsetdialog.setvisible(true); return; row[i] = color2int(buttons[i].getcolor()); // All colors are set for (int i=0; i<perrow; i++) buttons[i].setpassive(); try { game.setnextguess(row); catch (Exception e1) { // Impossible. int black = game.getnumberofblack(); int white = game.getnumberofwhite(); for (int i=0; i<black; i++) { result[i].setcolor(color.black); result[i].setvisible(true); for (int i=0; i<white; i++) { result[black+i].setcolor(color.white); result[black+i].setvisible(true); right.validate(); if (black == perrow) { // Game over! Problem solved! for (int i=0; i<perrow; i++) solution[i].setvisible(true); gameoversolveddialog.setvisible(true); return; if (next == null) { // Game over! Solution not reached! for (int i=0; i<perrow; i++) solution[i].setvisible(true); gameoverunsolveddialog.setvisible(true); return; // Otherwise, move to next round. gameround++; allsetb.setenabled(false); next.setvisible(true); ); // end of ActionListener add(allsetb); right = new Panel(); right.setlayout(new GridLayout(1,0)); result = new ColoredButton[perRow]; for (int j=0; j<perrow; j++) { result[j] = new ColoredButton(); right.add(result[j]); result[j].setvisible(false); right.setvisible(true); add(right); setvisible(false); // GameRow() public void setnext(gamerow gr) { next = gr; public void reset() { // Reset the game row. for (int i=0; i<perrow; i++) { result[i].setvisible(false); result[i].setcolor(color.gray); buttons[i].setvisible(true); buttons[i].reactivate(); buttons[i].setcolor(color.gray); allsetb.setenabled(true); // reset // class GameRow public class ColorChooser extends PopupMenu { public ColorChooser(Button b) { super("choose color"); for (int i=0; i<colors; i++) { MenuItem mi = new MenuItem(COLORNAMES[i]); add(mi); mi.addactionlistener(new ColorChoiceListener(b, COLORS[i])); // ColorChooser() // ColorChoose public class ColorChoiceListener implements ActionListener { private Button button; private Color color; public ColorChoiceListener(Button b, Color c) { button = b; color = c; public void actionperformed(actionevent e) { button.setbackground(color); // ColorChoiceListener public class GameOverDialog extends Dialog { private Label text; public GameOverDialog(Frame f, boolean b) { super(f, "Game over", true); setsize(350,100); if (b) text = new Label("Congratulations! You solved it!"); else text = new Label("Couldn t solve it this time!"); add(text, BorderLayout.CENTER); Button again = new Button("Again"); Button quit = new Button("Quit"); add(again, BorderLayout.WEST); add(quit, BorderLayout.EAST); quit.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { if (isapplet) { GameOverDialog.this.setVisible(false); MasterMindGame.this.setEnabled(false); return; else { System.exit(0); ); again.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { resetgame(); GameOverDialog.this.setVisible(false); ); // GameOverDialog(Frame f, boolean b) // class GameOverDialog // MasterMindGame

85 Luku 7 Johdatus Swing-sovelluksiin Graafinen Swing-kirjasto tuli Javan yhteyteen ns. Java Foundation Classes (JFC) -kokonaisuuden myötä. Swing ei ollut vielä osa JDK 1.1:tä, mutta sitä voitiin soveltaa sen yhteydessä JFC:n kautta. JDK 1.2:sta eteenpäin Swing on ollut osa JDK:ta. Swingin taustalla on Sunin lisäksi Netscapeyhtiö. AWT:n ominaisuudet nähtiin monien toimesta riittämättömiksi niillä ei saanut aikaan kaikkea sitä, mitä tavallisimpien ikkunointijärjestelmiin liittyvillä kirjastoilla (C / C++). Netscape alkoi kehittämään omia GUI-komponentteja ja niihin liittyviä luokkia kokonaisuutta se kutsui Internet Foundation Classes -nimellä. Sun huolestui tästä kehityksestä, ja aloitti yhteistyön, jonka tuloksena syntyi JFC. Jatkossa AWT:tä ei enää ole tarkoitus kehittää kehitystyö suunnataan pikemminkin Swingiin. Swing koostuu yli 600 luokasta (JDK 1.6), rajapinnat mukaan lukien. JFC puolestaan koostuu Swingin lisäksi muista kokonaisuuksista. Swing rakentuu AWT:n päälle, joten sen ei ole tarkoitus täysin syrjäyttää AWT:tä. Swingin ja AWT:n pääasialliset erot ovat seuraavat. Swingissä on enemmän graafisia komponentteja kuin AWT:ssä. Joidenkin tahojen mukaan moninkertaisesti enemmän. Swing-komponentteja on n. 40 kpl, kun AWT:ssä varsinaisesti on vain n. 15 komponenttia. Osa erosta selittyy sillä, että menuihin liittyviä luokkia ei AWT:ssä lueta komponentteihin Swingissä luetaan. Itse asiassa, AWT:hen verrattuna Swingissä on vain muutama täysin uudenlainen komponentti. Toisaalta Swingissä on useita samantapaisia komponentteja, esimerkiksi liu uttimia on useita. Swing-komponenttien nimet alkavat J -kirjaimella, esimerkiksi JButton, JFrame ja JText- Field. Swing-komponentit ovat ominaisuuksiltaan monipuolisempia. Esimerkiksi, komponentteihin voidaan liittää erilaisia dekoratiivisia reunustuksia (border). Lisäksi kaikkiin komponentteihin voidaan liittää tekstivihje ja pikanäppäimiä. Yleensäkin tarkoitus on, että Swing-sovelluksia voi suorittaa tarpeen vaatiessa täysin ilman hiirtä. Suurin osa Swing-komponenteista on keveitä. Tämä tarkoittaa, että niihin ei liity vastinoliota, vaan komponentit piirtävät itsensä jonkin raskaan komponentin taustaa vasten. Swingin komponenteista vain neljä on raskaita: päätason komponenttisäiliöluokat JApplet, JWindow, JFrame ja JDialog. Ks. kuva 7.1. Keveydellä tavoitellaan tehokkuutta ja Swingin komponentit ovatkin tehokkaampia kuin AWT:n vastaavat. 85

86 86 LUKU 7. JOHDATUS SWING-SOVELLUKSIIN Swing-komponentteihin liittyy malli. Mallin tehtävänä on esittää komponentin tilainformaatiota. Malli on oma luokkansa ja siihen on mahdollista päästä käsiksi. Itse asiassa monien komponenttien kohdalla olennainen osa toiminnan määrittelyä on komponentin malliolion määrittely uudestaan perinnän avulla (esim. JTable). Monet Swing-komponentit käyttävät saman tyyppistä mallia, joka mahdollistaa malliolion jakamisen kahden tai useamman GUI-komponentin kesken. Mallin myötä Swing tukee ns. Model-View-Controller-suunnitteluperiaatetta (MVC) noudattaen sitä itsekin. Erityisesti, komponenteilla on myös view-olio ja itse komponetti on kontrollerin roolissa. Edellisten lisäksi Swing mahdollistaa komponenttien graafisen ulkoasun (look-and-feel) määräämisen käytettävästä ikkunointijärjestelmästä riippumatta. AWT:n vika tässä mielessä on, että komponenttien ulkoasu on erilainen eri käyttöjärjestelmäalustoilla. Swingissä oletusarvoisesti käytetään Metal-ulkoasua, mutta käytettävissä ovat myös Motif- ja Windows-ulkoasut (edelliset ovat osa JDK:ta useita muitakin on). Esimerkissä 7.1 näytetään, miten komponenttien ulkoasua voidaan vaihtaa suorituksen aikana. Muutokset voidaan tehdä kokonaisvaltaisesti tai komponenttikohtaisesti. Object Component Container Panel Applet JWindow JApplet = raskas = Swing-komp Window Frame Dialog JFrame JDialog = abstrakti luokka = kevyt JComponent JRootPane JPanel JMenuBar AbstractButton Kuva 7.1: Swing-luokkien sijoittuminen AWT:n hierarkiaan. Esimerkissä 7.1 luodaan Swing-tyyppinen kehysolio, johon sijoitetaan joukko erilaisia Swingkomponentteja. Sovellukseen liittyy kolme nappulaa, joihin kuhunkin liitetään LookAsettaja -tyyppinen tapahtumankäsittelijä, joka nappulan painalluksen seurauksena vaihtaa kaikkien komponenttien ulkoasua. Tapahtumankäsittelijä saa tarpeellisen informaation konstruktorin parametrin kautta: ulkoasua voidaan vaihtaa luokan UIManager metodilla setlookandfeel eräs metodin käyttötavoista on LookAndFeel-tyyppisen luokan nimen antaminen. Huomaa, miten actionperformed :n lopussa uutta ulkoasua sovelletaan kaikkiin komponentteihin. Luotava kehysolio on muodostettu perimällä luokasta JFrame, vaikka perinnän yhteydessä ei määritelläkään mitään metodeja uudestaan. Edellisestä joh-

87 87 tuen käyttöliittymän muodostaminen tapahtuu konstruktorin rungossa, main -metodissa riittää vain luoda kehysolio (ja sen seurauksena kaikki muutkin oliot) ja tapahtumankäsittelijäoliot ovat sisäluokan mukaisia oliota (mistä johtuen instanssimuuttuja alue on käytettävissä mahdollisen poikkeuksen syyn tulostamiseen). Kuva 7.2: Esimerkin 7.1 ohjelma Motif-ulkoasulla. Kuva 7.3: Esimerkin 7.1 ohjelma Metal-ulkoasulla.

88 88 LUKU 7. JOHDATUS SWING-SOVELLUKSIIN Esimerkki 7.1 LookAndFeel-ominaisuuden vaihtaminen kesken sovelluksen suorituksen. import java.awt. ; import java.awt.event. ; import javax.swing. ; import javax.swing.plaf.metal.metallookandfeel; import com.sun.java.swing.plaf.motif.motiflookandfeel; import com.sun.java.swing.plaf.windows.windowslookandfeel; public class AlustaDemo extends JFrame { private JTextArea alue = new JTextArea("alku", 30,30); public static void main(string[] args) { new AlustaDemo().setVisible(true); public AlustaDemo() { settitle("lookandfeel demo"); setsize(400,400); addwindowlistener(new IkkunanSulkija()); JButton m1 = new JButton("Metal"); JButton m2 = new JButton("Motif"); JButton m3 = new JButton("Windows"); JPanel paneeli = new JPanel(); paneeli.add(m1); paneeli.add(m2); paneeli.add(m3); paneeli.add(new JTextField("Tekstikenttä")); paneeli.add(new JCheckBox("Valintalaatikko")); paneeli.add(new JLabel("Leima")); paneeli.add(new JRadioButton("Radionappula")); String[] lista = { "yksi", "kaksi", "kolme" ; paneeli.add(new JList(lista)); JRadioButton b1 = new JRadioButton("1998"); JRadioButton b2 = new JRadioButton("1999"); JRadioButton b3 = new JRadioButton("2000"); ButtonGroup g = new ButtonGroup(); g.add(b1); g.add(b2); g.add(b3); paneeli.add(b1); paneeli.add(b2); paneeli.add(b3); paneeli.add(alue); Container pane = getcontentpane(); pane.add(paneeli); m1.addactionlistener(new LookAsettaja("javax.swing.plaf.metal.MetalLookAndFeel")); m2.addactionlistener(new LookAsettaja ("com.sun.java.swing.plaf.motif.motiflookandfeel")); m3.addactionlistener(new LookAsettaja ("com.sun.java.swing.plaf.windows.windowslookandfeel")); private class LookAsettaja implements ActionListener { private String look; public LookAsettaja(String l) { look = l; public void actionperformed(actionevent e) { try { UIManager.setLookAndFeel(look); catch (Exception e1) { alue.append(e1.tostring()); SwingUtilities.updateComponentTreeUI(AlustaDemo.this); // LookAsettaja // AlustaDemo

89 7.1. SWING-KOMPONENTEISTA Swing-komponenteista Seuraavassa lyhyt esittely Swing-komponenteista (paketissa javax.swing). Useita seuraavista tarkastellaan yksityiskohtaisesti luvussa Komponenttisäiliöt Swingiin ei liity JContainer-nimistä luokkaa, joka määrittelisi komponenttisäiliöiden yleisen muodon. Itse asiassa Swingin säiliöiden toimintaidea on hieman erilainen kuin AWT:n säiliöiden. Taulukossa 7.1 on lyhyt luonnehdinta erilaisista komponenttisäiliöistä tarkemmin asiaa käsitellään kohdassa 7.2. JApplet: Applet:n laajennettu versio, joka tukee mm. menuvalikoita. JDesktopPane: Eräänlainen komponenttisäiliö. JDialog: Dialogi-ikkuna. JFrame: Kehys (perii Frame:sta), jossa erilaisia komponenttisäiliöitä; ks. kohta 7.2. JInternalFrame: Edellisen kevyt versio. JLayeredPane: Mahdollistaa komponenttien järjestämisen tasoihin. JOptionPane: Apuluokka, jonka avulla voidaan muodostaa räätälöityjä dialogi-ikkunoita. JPanel: Komponenttisäiliö, joka toimii tarvittaessa myös piirtopintana ( korvaa AWT:n luokat Panel ja Canvas). JRootPane: Eräänlainen pääsäiliö komponenteille. JScrollPane: Säiliö, jonka reunoilla liu uttimet. Monissakaan Swingin komponenteissa ei ole automaattisesti liu uttimia, jolloin sellaiset saadaan tämän avulla. JTabbedPane: Säiliö, joka toteuttaa CardLayout:n tapaisen toiminnallisuuden. JWindow: Samantapainen kuin JFrame, mutta ei otsikkoa. Itsenäinen ikkuna. Taulukko 7.1: Swingin säiliöluokkia AWT:n peruskomponentteja vastaavat komponentit Taulukossa 7.2 on kuvattu lyhyesti ne Swingin komponentit, joilla on suoraan vastine AWT:ssä. Komponentteja tarkastellaan lähemmin luvussa 8. JButton: Nappula, jota painamalla aiheutuu ActionEvent-tapahtuma; voi sisältää myös kuvan. JCheckBox: Valintalaatikko, joka voidaan rastittaa. Voi sisältää kuvan. JCheckBoxMenuItem: Menun alkio, joka on valintalaatikko. JComponent: Swing-komponenttien yliluokka (joka perii luokasta Container!). JFileChooser: Komponentti tiedoston nimen valitsemiseen. JFormattedTextField: Tietyllä tavalla muotoiltu (sisältö) tekstikenttä. JLabel: Leima, joka voi sisältää kuvankin. JList: Valintalista (voidaan valita useita). JMenu: Ponnahdusvalikko, joka sisältää menualkioita (menupalkissa). JMenuBar: Menupalkki. JMenuItem: Menualkio.

90 90 LUKU 7. JOHDATUS SWING-SOVELLUKSIIN JPasswordField: Tekstikenttä salasanan kirjoittamista varten. JPopupMenu: Ponnahdusvalikko. JRadioButton: Radionappula. Voi sisältää kuvan. JRadioButtonMenuItem: Radionappula menun osana. JScrollBar: Liukuvalitsin. JSeparator: Menuvalikon erotin. JSlider: Toisenlainen liukuvalitsin. JTextArea: Monirivinen tekstialue. JTextField: Yksirivinen tekstikenttä. Taulukko 7.2: Swingin GUI-komponentit, jotka vastaavat jotain AWT:n komponenttia Täysin uudenlaiset GUI-komponentit Taulukossa 7.3 luetellaan Swingin GUI-komponentteja, joilla ei ole suoranaista vastinetta AWT:ssä. Näitäkin komponentteja tarkastellaan lähemmin luvussa 8. JColorChooser: Monipuolinen työkalu värin valitsemiseksi. Tieto valinnasta tallettuu osaksi komponenttia. JComboBox: Monipuolinen tekstikentän ja pudotusvalikon yhdistelmä. Jos valikossa ei ole sopivaa kohtaa, on mahdollista itse kirjoittaa sopiva valinta. JEditorPane: Tekstin editointikomponentti. JProgressBar: Esittää havainnollisesti lukuja lukuväliltä (tyyliin 89% loaded...). JSplitPane: Kahden komponentin sijoittelu toistensa suhteen ( liimaus kiinni toisiinsa). JSpinner: Arvon valintatyökalu. Nykyistä arvoa voi pyörittää seuraavaan molemmissa suunnissa. JTable: Esittää tietoa 2-uloitteisen taulukon muodossa; erittäin monipuolinen ja hyödyllinen. JTextPane: Hyvin monipuolinen tekstialue. JToggleButton: 2-tilainen nappula (ylhäällä, pohjassa). JToolBar: Työkalupalkki. JToolTip: Komponentteihin liitettävä vihjeteksti. JTree: Hierarkinen puurakenne. Erittäin monipuolinen ja hyödyllinen luokka. JViewport: Näkymä isompaan kokonaisuuteen. Taulukko 7.3: Swigin täysin uudenlaiset komponentit. 7.2 Komponenttisäiliöistä AWT:hen nähden Swingin komponenttisäiliöt ovat paljon tasa-arvoisempia. Ne kaikki sisältävät JRootPane-luokan olion, juuriruudun (root pane) säiliöön lisättävät komponentit tulee liittää kyseisen olion yhteyteen (säiliöruutuun tai kerroksisuusruutuun). JFrame: Reunat ja otsikkopalkki. JWindow: Ei osikkopalkkia eikä reunoja mutta on itsenäinen olio (ei tarvitse tuekseen kehystä kuten AWT:ssä). JApplet: Kuten Applet, mutta sisältönä JRootPane. JDialog: Kuten Dialog, mutta sisältönä JRootPane.

91 7.2. KOMPONENTTISÄILIÖISTÄ 91 Kuvan 7.1 perusteella kaikki edelliset luokat perivät AWT:n luokasta Container, mistä johtuen niillä on add -metodi komponenttien lisäämistä varten. Kyseistä add -metodia ei kuitenkaan tulisi käyttää 1, vaan komponentit pitää lisätä juuriruudun osana olevaan säiliöruutuun. Juuriruudulla on rakenne, joka on esitetty kuvassa 7.5. Juuriruutu koostuu kahdesta osasta: kerroksisuusruudusta (layered pane) ja lasiruudusta (glass pane). Lasiruutu on Component-luokan olio ja katsojan kannalta lasiruutu sijaitsee koko komponenttisäiliön päällä. Oletusarvoisesti lasiruutu ei ole näkyvissä, mutta näkyvissä ollessaankin se on läpinäkyvä! Lasiruutua käytetään lähinnä siihen, että sen avulla voidaan estää käyttäjän tapahtumien kulkeutuminen säiliön komponentteihin. Jos lasiruutuun piirtää jotakin, niin se tulee koko sovelluksen päälle. Kerroksisuusruutu koostuu menupalkista, JMenuBar-tyyppinen olio, ja säiliöruudusta (content pane), Container-tyyppinen olio. Jos komponenttien halutaan sijaitsevan samalla tasolla, kerroksisuusruutu ohitetaan ja komponentit lisätään suoraan säiliöruutuun tämä on tavallista. Jos komponentteja halutaan sijoittaa eri kerroksiin, niin komponentit pitää lisätä osaksi kerroksisuusruutua. Esimerkissä 7.2 luodaan kaksi kehystä, pääikkuna ja koeikkuna. Pääikkunaan laitetaan kolme nappulaa, joissa on sisältönä tekstin lisäksi tietyistä URL-osoitteista haettuja kuvia. Nappuloiden avulla säädellään, toimivatko koeikkunaan laitetut komponentit vai eivät. Toimivuudella tarkoitetaan, että pääsevätkö käyttäjän aiheuttamat tapahtumat vaikuttamaan kyseisiin komponentteihin. Sääteleminen tapahtuu lasiruudun avulla: estä -nappula tekee koeikkunan lasiruudusta näkyvän, jolloin siihen kiinnitetty tapahtuman käsittelijä estää hiiritapahtumien kohdistumisen säiliön komponentteihin. Huomaa, miten klikkauskuluttaja -muuttujaan luotu sisäluokan mukainen tapahtumankäsittelijä osaa kuluttaa kolmenlaisia hiiritapahtumia. Nappulan salli merkitys on puolestaan lasiruudun muuttaminen näkymättömäksi, jolloin tapahtumat pääsevät etenemään normaalisti. Huomaa, miten nappuloihin estä ja salli kiinnitetyt tapahtumankäsittelijät tarvitsevat tietoa luokkamuuttujasta koeikkuna. Sovelluksen lopettava tapahtumankäsittelijä on toteutettu myös anonyymillä sisäluokalla ja se on kiinnitetty kolmanteen nappulaan. Koeikkunaan on sijoitettu kolme komponettia: värivalintatyökalu, radionappula ja tekstikenttä. Värivalintatyökaluun kiinnitetty tapahtumankäsittelijä muuttaa pääikkunan taustan väriä (tieto pääikkunaoliosta välitetään staattisen anonyymin sisäluokan mukaiselle tapahtumankäsittelijälle luokkamuuttujan pääikkuna välityksellä). Kuva 7.4: Esimerkin 7.2 ulkoasu. 1 Metodin add soveltaminen tuottaa virheilmoituksen JDK:n versioissa, jotka edeltävät 1.5:ttä. JDK 1.5:ssä add - metodit (ja muutkin vastaavat metodit) on korjattu soveltamaan lisäystä säiliöruutuun. Lisäys on tehty as a convenience...

92 92 LUKU 7. JOHDATUS SWING-SOVELLUKSIIN Esimerkki 7.2 Demonstraatio, jossa sovellusikkunan päällä oleva lasiruutu kuluttaa tapahtumat. import java.awt. ; import java.awt.event. ; import javax.swing. ; import javax.swing.event. ; import java.net. ; public class TestGlassPane { private static final String quits = " private static final String prevents = " private static final String continues = " private static JFrame pääikkuna, koeikkuna; private static MouseAdapter klikkauskuluttaja; public static void main(string[] args) { koeikkuna = teekoeikkuna(); pääikkuna = new JFrame("Pääikkuna"); ImageIcon quiticon, stopicon, go_onicon; try { quiticon = new ImageIcon(new URL(quitS)); catch (MalformedURLException e) { quiticon = null; try { stopicon = new ImageIcon(new URL(preventS)); catch (MalformedURLException e) { stopicon = null; try { go_onicon = new ImageIcon(new URL(continueS)); catch (MalformedURLException e) { go_onicon = null; JButton b1 = new JButton("Estä", stopicon); JButton b2 = new JButton("Salli", go_onicon); JButton b3 = new JButton(quitIcon); Container pane = pääikkuna.getcontentpane(); pane.setlayout(new FlowLayout(FlowLayout.CENTER, 5, 5)); pane.add(b1); pane.add(b2); pane.add(b3); b3.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { System.exit(0); ); klikkauskuluttaja = new MouseAdapter(){ public void mouseclicked(mouseevent e) { e.consume(); public void mousepressed(mouseevent e) { e.consume(); public void mousereleased(mouseevent e){ e.consume(); ; koeikkuna.getglasspane().addmouselistener(klikkauskuluttaja); b1.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { koeikkuna.getglasspane().setvisible(true); ); b2.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { koeikkuna.getglasspane().setvisible(false); ); pääikkuna.pack(); pääikkuna.setlocation(500,500); pääikkuna.setvisible(true); koeikkuna.pack(); koeikkuna.setvisible(true); // main private static JFrame teekoeikkuna() { JFrame f = new JFrame("Koeikkuna"); Container pane = f.getcontentpane(); pane.setlayout(new FlowLayout()); final JColorChooser cc = new JColorChooser(); pane.add(cc); pane.add(new JRadioButton("Radionappula")); pane.add(new JTextField("Alustettu tekstikenttä")); cc.getselectionmodel().addchangelistener(new ChangeListener() { public void statechanged(changeevent e) { pääikkuna.getcontentpane().setbackground(cc.getcolor()); ); return f; // teekoeikkuna // TestGlassPane

93 7.2. KOMPONENTTISÄILIÖISTÄ 93 MenuBar LayeredPane Content pane (Container) RootPane Glass pane (Component) Kuva 7.5: Swingin komponenttisäiliöiden ruuturakenne. Esimerkki 7.3 havainnollistaa, miten komponentteja voi sijoitella eri kerroksiin. Samalla havainnollistetaan komponenttien läpinäkyvyyttä ja komponenttien sijoittelua ilman ikkunamanageria. Ikkunamanagerin voi poistaa asettamalla sen setlayout -metodilla null:ksi. Tuolloin komponentit pitää sijoitella niin, että itse määrätään komponentin setlocation -metodilla, minne komponentti sijoitetaan. Esimerkissä havainnollistetaan viittä eri kerrosta: DEFAULT_LAYER (oletus), PALETTE_LAYER (esim. työkalupalkki on tässä kerroksessa), MODAL_LAYER (modaaliset dialogit), POPUP_LAYER (popup-menut) ja DRAG_LAYER (komponenttien raahaaminen). Edellinen luettelo esittää kerroksia järjestyksessä alimmasta ylimpään. Kuhunkin kerrokseen luodaan esimerkissä yksi komponentti. Luodut komponentit (nappulat) ovat läpinäkyviä ja osittain päällekkäisiä. Komponenttien näkyvyyden muuttamista kannattaa kokeilla ( näkyvyys - taulukon arvot). Huomaa, että jos rivin jcp.add(nappulat[i]); tilalla käytetään riviä cp.add(nappulat[i]);, niin komponentteihin ei liity kerroksellisuutta, vaan ne kaikki sijaitsevat samalla oletuskerroksella. Kerroksellisuus on käytettävissä vain, jos komponentit lisätään JLayeredPane-tyyppiseen säiliöön. Jokaiseen komponenttiin liitetyn tapahtumankäsittelijän avulla voi havainnoida, milloin hiiri on komponentin alueella tästä voi päätellä, milloin komponentti on toisen päällä!

94 94 LUKU 7. JOHDATUS SWING-SOVELLUKSIIN Esimerkki 7.3 Komponenttien sijoittelua eri tasoihin. import java.awt. ; import java.awt.event. ; import javax.swing. ; import javax.swing.border. ; public class LayersDemo { private static boolean[] näkyvyys = { true, true, true, true, true ; private static Integer[] kerrokset = { JLayeredPane.DEFAULT_LAYER, JLayeredPane.PALETTE_LAYER, JLayeredPane.MODAL_LAYER, JLayeredPane.POPUP_LAYER, JLayeredPane.DRAG_LAYER ; public static void main(string[] args) { JFrame frame = new JFrame("LayersDemo"); frame.setsize(400,300); Container cp = frame.getcontentpane(); JLayeredPane jcp = frame.getlayeredpane(); cp.setlayout(null); JButton[] nappulat = new JButton[5]; Border etcborder = BorderFactory.createEtchedBorder(); for (int i=0; i<5; i++) { nappulat[i] = new JButton("Nappula " + i); nappulat[i].setopaque(näkyvyys[i]); nappulat[i].setborder(etcborder); jcp.add(nappulat[i]); nappulat[i].setbounds(40 i,40 i,100,60); JTextField t = new JTextField("xx "); cp.add(t); t.setbounds(60 i,250,50,20); t.setvisible(true); nappulat[i].addmousemotionlistener( new NappulaKuuntelija(t)); jcp.setlayer(nappulat[i], kerrokset[i].intvalue()); frame.setvisible(true); // main static class NappulaKuuntelija extends MouseMotionAdapter { private JTextField tulos; NappulaKuuntelija(JTextField tf) { tulos = tf; public void mousemoved(mouseevent e) { tulos.settext("("+e.getx()+","+e.gety()+")"); // class NappulaKuuntelija // class LayersDemo

95 7.3. TAPAHTUMISTA JA TAPAHTUMANKÄSITTELYSTÄ 95 Kuva 7.6: Esimerkin 7.3 havainnollistus. 7.3 Tapahtumista ja tapahtumankäsittelystä Tapahtumankäsittely on hyvin samanlaista Swing-komponenttien kohdalla kuin AWT-komponenttienkin kohdalla. Itse käsittelymekanismi on täsmälleen sama! Olennaisin muutos on, että Swingin myötä tulee n. 15 uutta tapahtumaa sekä tietysti niitä vastaavat käsittelyrajapinnat ja adapteriluokat sekä metodit käsittelijöiden kiinnittämiseksi komponenttiin (ja poistamiseksi). Swing-komponenttien toiminta perustuu myös AWT:ssä määriteltyihin tapahtumiin. Swing ei siis tässä mielessä syrjäytä AWT:tä, vaan vain pyrkii täydentämään sitä. Toisaalta, muutamissa kohdin olisi voitu käyttää AWT:n tapahtumaa, mutta Swingiin on esitelty uusi tapahtuma sitä varten. Tämä on sikäli harmillista, että pitää hyvin tarkkaan muistaa, mitä tapahtumia mikäkin komponentti käsittelee (ja millainen tapahtuma minkäkin matalan tason tapahtuman seurauksena syntyy). Swingin tapahtumat ovat pääsääntöisesti paketissa javax.swing.event. Itse asiassa Swing määrittelee kaksi uutta matalan tason tapahtumaa: KeyEvent:n aliluokan MenuKeyEvent ja luokan MouseEvent uuden aliluokan MenudragEvent. Muut Swingin tapahtumat ovat semanttisia tapahtumia, jotka perivät luokasta java.util.eventobject. Poikkeuksen edelliseen muodostavat luokat AncestorEvent ja InternalFrameEvent, jotka perivät luokasta java.awt.awtevent. Taulukossa 7.4 on lyhyesti kuvattu, millaisia muutamat Swingin uudet tapahtumat oikein ovat. 7.4 Lopuksi Swing on AWT:n valittujen osien päälle tehty monipuolisempi ja tehokkaampi GUI-komponenttien kokonaisuus, jonka on tarkoitus syrjäyttää AWT GUI-sovellusten tekemisessä. Swing-komponentit noudattavat MVC-suunnittelumallia ja toisaalta lähes kaikki Swing-komponentit ovat keveitä. Swingin komponenttisäiliöt (JFrame, JApplet, JWindow ja JDialog) toimivat varsin eri tavalla kuin

96 96 LUKU 7. JOHDATUS SWING-SOVELLUKSIIN AncestorEvent: Johonkin komponentin sisältävään säiliöön on tapahtunut jokin muutos. CaretEvent: Tekstikohdistimen positio tekstikomponentissa on muuttunut. ChangeEvent: Komponentin tila on muuttunut. Vrt. AdjustmentEvent. HyperlinkEvent: Liittyy HTTP-yhteyden käyttöön; jokin liittyen hyperlinkkiin on muuttunut. ListDataEvent: Mallin laukaisema tapahtuma (ei komponentin); listan tila muuttunut. ListSelectionEvent: Tehty valinta tai valinnan purku. Vrt. ItemEvent. MenuEvent: Menuun on kohdistunut jokin tapahtuma (valinta tms). PopupMenuEvent: Popup-menun esiintuomiseen ja poistamiseen liittyviä tapahtumia. TreeExpansionEvent, TreeSelectionEvent, TreeModelEvent: Hierarkiseen puurakenteeseen liittyviä tapahtumia (laajennos, valinta, mallin tila muuttunut). TableModelEvent: Taulukko-komponentin mallissa on tapahtunut muutos! Taulukko 7.4: Muutamia Swingin tapahtumia. AWT:n vastaavat säiliöt. Vaikka Swingin mukana tuleekin paljon monipuolisempia komponentteja, tapahtumankäsittelymekanismi on sama kuin AWT:ssäkin. Swing- ja AWT-komponentteja voidaan käyttää yhdessä niitä voidaan esim. laittaa samaan Swing-pohjaiseen komponenttisäiliöön. Tuolloin pitää kuitenkin muistaa keveiden ja raskaiden komponenttien ero: kevyet vain piirtävät itsensä säiliön piirtopinnalle, kun raskaat komponentit esitetään ikkunointijärjestelmän vastionolioilla. Vastinoliot piirtyvät aina säiliön taustan päälle!

97 Luku 8 Swing-komponentit Tässä luvussa tehdään melko pintapuolinen katsaus moniin Swingin komponentteihin. AWT:n kohdalla komponentteja tarkasteltiin perusteellisesti. Nyt tarkoituksena on todeta, millaisia ominaisuuksia Swingin vastaavilla komponenteilla on missä mielessä ne ovat parempia / erilaisia. Osalla Swingin komponentteja ei ole vastinetta AWT:ssä (esim. JTable ja JTree) näiden kohdalla ominaisuuksia tarkastellaan perusteellisemmin. Osa uusista Swing-komponenteista ohitetaan (mm. JViewport ja JSpinner). Tarkastelu aloitetaan leimasta luokan JLabel kautta luvussa 8.1. Tässä yhteydessä sivutaan luokkia Icon ja ImageIcon, koska Swingissä komponentit voivat tekstin lisäksi sisältää kuvia. Seuraavaksi luvussa 8.2 tarkastellaan Swingin tekstikenttä ja -editorikomponentteja: JTextComponent, JText- Field, JPasswordField, JTextArea, JEditorPane, JTextPane. Swingin nappuloita ja niihin rinnastettavia komponentteja käsitellään luvussa 8.3 luokkien AbstractButton, JButton, JToggleButton, JCheckBox, JRadioButton ja ButtonGroup kautta. Liu uttimia tarkastellaan luokkien JScrollBar, JSlider ja JScrollPane kannalta luvussa 8.4. Listoja käsitellään vastaavasti luvussa 8.5 luokkia JList ja JComboBox tarkastelemalla. Swingin toiminnan tukena on myös suurehko joukko muita sekalaisia GUI-komponentteja ja apuluokkia. Näistä luokkia JProgressBar, JToolTip, JSeparator, JColorChooser ja JFileChooser tarkastellaan luvussa 8.6. Layout-asiaa asiaa käsitellään hieman luvussa 8.7 luokkien JTabbedPane, JSplitPane ja BoxLayout avulla. AWT:hen verrattuna menuiden asemaa on parannettu ja lisäksi on esitelty ns. työkalupalkit näitä tarkastellaan luvussa 8.8. Dialogi-ikkunoihin liittyviä GUI-komponentteja käsitellään luvussa 8.9. Tämän jälkeen käsitellään luvussa 8.10 yhtä Swingin monipuolisimmista GUI-komponenteista, nimittäin luokkaa JTable. Vastaavalla tavalla uusi ja monipuolinen komponentti JTree on luvun 8.11 aiheena. Lopuksi pienimuotoinen yhteenveto tehdään luvussa Leima JLabel Leima on yksinkertainen peruskomponentti, joka perii luokasta JComponent. Sen ominaisuuksia ovat mm. tekstisisältö, ikonikuva (Icon) ja vaaka- sekä pystysuuntaisen kohdistuksen arvot. Luokan konstruktorilla on useita muotoja, joilla ilmoitetaan erilaisia arvojen yhdelmiä. Näkyvänä sisältönä on joko ikonikuva, teksti tai ikoni sekä teksti. Tekstin ja ikonin suhteellinen sijainti voidaan määrätä. Ikonit liittyvät leimaan rajapinnan Icon avulla rajapinnan toteuttaa luokka ImageIcon, jonka avulla voi luoda ikonin esimerkiksi GIF- tai JPEG-kuvasta. Kuvan liittäminen komponenttiin on yleinen Swingin mukana tullut laajennos ideana on, että jos AWT:n kohdalla oli jossakin komponentissa käyttäjälle näkyvä teksti, niin lähes kaikkiin sellaisiin 97

98 98 LUKU 8. SWING-KOMPONENTIT komponentteihin on mahdollista asettaa tekstin sijaan/rinnalle kuva. Toinen yleinen Swingin kuviin liittyvä laajennos on, että komponenttiin on mahdollista asettaa useita kuvia erilaisia komponenttiin liittyviä tilanteita varten. Esimerkiksi, JLabel:ssa on mahdollista asettaa toinen ikoni komponentin disabled -tilaa varten. Luonnollisesti edellä kuvatuille leiman ominaisuuksille on vastaavat get- (havainnointi) ja setmetodit (muuttaminen). Kaikkiin Swingin GUI-komponentteihin liittyy mahdollisuus määrätä komponentti, jonka avulla tuotetaan graafinen ulkoasu (ns. rederöijä; Look&Feel:n asettaminen). Metodilla setui voidaan asettaa uusi ulkoasun tuottava olio (tyyppiä LabelUI). Vastaavasti getui :llä voi havainnoida asiaa. Metodilla updateui voi tuottaa päivitetyn ulkoasun. 8.2 Tekstikentät Leimaan ei liity MVC-ajattelun mukaista mallioliota, mutta useimpiin Swingin komponentteihin sellainen liittyy. Erityisesti kaikilla tekstikomponenteilla on malliolio! Se on kaikkien erilaisten tekstikomponenttien yhteydessä samaa tyyppiä: Document (rajapinta). Huomaa, että erillisen malliolion olemassaolo tarkoittaa myös sitä, että yksittäinen malliolio on mahdollista jakaa usean komponentin kesken! Malliolio sisältää komponentin datan, joten jakamistilanteessa sama data on usean komponentin sisältönä ja yhteen tehty muutos heijastuu välittömästi toisiin samaa mallioliota käyttäviin GUI-komponentteihin. Malliolion voi asettaa metodilla setdocument ja sitä voi havainnoida metodilla getdocument. Useimmat Swingin tekstikomponentit ovat hyvin samanlaisia kuin AWT:n vastaavat. Muutokset ovat pääasiallisesti kirjattu kaikkien tekstikomponenttien yliluokaksi määritettyyn luokkaan javax. swing.text.jtextcomponent, ja merkittävin muutos on malliolion mukaantulo. Malliolion lisäksi Look&Feel:n asettaminen on samoin kuin leiman kohdallakin ja lisäksi malliolion kautta tekstikomponentteihin on mahdollista rakentaa undo/redo -toiminnot (mutta niitä ei oletusarvoisesti ole). Kursorin kohdistuksiin on mahdollista reagoida tapahtumankäsittelijöillä. Seuraavassa lyhyt kuvaus muista Swingin tekstikomponenteista: JTextField On hyvin samanlainen TextField:n kanssa. Yksirivinen tekstikomponentti, joka sisältää vain tekstiä (ei kuvia). Enter :n painallus komponentin yhteydessä aiheuttaa tapahtuman ActionEvent. JPasswordField Tämä luokka on peritty JTextField:stä ja sen erikoisuus edelliseen verrattuna on, että kaiutus on salattu. JTextArea Tämä komponentti toimii kuten TextArea, sisältää vain tekstiä. JEditorPane Komponentti, jonka avulla voi editoida erilaisia tekstidokumentteja (html, rtf). JTextPane Perii edellisestä komponentista ja on vieläkin monipuolisempi. Tekstiä, kuvia, komponentteja,... Esimerkkejä Seuraavaksi esitetään kaksi esimerkkiä tekstikomponenttien käytöstä. Kuvassa 8.1 on esitettynä esimerkissä 8.1 olevan ohjelman JTextDemo ulkoasu. Esimerkin yläosassa on 4 tekstikenttää. Ensimmäiseen kirjoitetun URL:n perusteella haetaan WWWsivu kuvan alaosassa olevan JEditorPane-komponentin sisällöksi ensimmäiseen tekstikenttään on laitettu tapahtumankäsittelijä, jonka laukeaminen asettaa aina uuden WWW-sivun voimaan. Huomaa,

99 8.2. TEKSTIKENTÄT 99 miten JEditorPane-tyyppinen komponentti pystyy esittämään erilaisia graafisia komponentteja, joita WWW-sivuun on voitu upottaa. Nimensä mukaan JEditorPane on editori, jolla voi malliolioksi ladatun dokumentin sisältöä muuttaa kokeile lisäämistä ja poistamista. Toinen ja neljäs tekstikenttä on liitetty toisiinsa siten, että ne jakavat keskenään yhteisen malliolion. Riippumatta siitä, kumpaan komponenttiin editointi (kirjoittaminen) kohdistuu, niin molempien sisältönä on sama merkkijono. Neljäs kenttä on JPasswordField-tyyppinen, joten sen sisältö ei kaiutu selväkielisenä sen sijaan toinen tekstikenttä on normaalia JTextField-tyyppiä. Kolmas tekstikenttä on myös salasanakenttä. Neljän tekstikentän muodostaman kokonaisuuden ja JEditorPanetyyppisen komponentin välissä on vielä JTextArea-tyyppinen komponentti. Kuva 8.1: JTextDemo suorituksessa.

100 100 LUKU 8. SWING-KOMPONENTIT Esimerkki 8.1 Swingin tekstikomponenttien havainnoillistamista. import javax.swing. ; import java.net. ; import java.awt. ; import java.awt.event. ; public class JTextDemo { // Tekstikomponenttien JTextField, JPasswordField, // JTextArea, JEditorPane havainnollistusta. private static JEditorPane ep; private static JLabel statusrivi; public static void main(string[] args) { JFrame f = new JFrame("Tekstikomponenttien havainnollistus"); final Container c = f.getcontentpane(); c.setlayout(new FlowLayout()); JLabel wwwhopute = new JLabel("Anna WWW-sivun URL:"); JTextField tf1 = new JTextField(" JTextField tf2 = new JTextField(20); JPasswordField pw1 = new JPasswordField(20); JPasswordField pw2 = new JPasswordField(20); tf2.setdocument(pw2.getdocument()); // Sama malli! tf2.setborder(borderfactory.createlineborder(color.green)); pw2.setborder(borderfactory.createlineborder(color.red)); JTextArea ta = new JTextArea(5, 40); // Liitetään tekstikentään toiminto, jonka avulla // sen URL-sisällön kautta haetaan sivu EditorPane:een. tf1.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { String sivu = ((JTextField)e.getSource()).getText(); try { ep.setpage(sivu); statusrivi.settext("uusi sivu asetettu!"); catch (Exception ee) { statusrivi.settext("sivun " + sivu + "asettaminen ei onnistunut"); ); statusrivi = new JLabel("Status: odotetaan sivun määritystä"); c.add(wwwhopute); c.add(tf1); c.add(tf2); c.add(pw1); c.add(pw2); c.add(ta); c.add(statusrivi); ep = new JEditorPane(); JScrollPane sp = new JScrollPane(ep); sp.setpreferredsize(new Dimension(550,400)); c.add(sp); f.setsize(700,800); f.setvisible(true); f.addwindowlistener(new IkkunanSulkija()); // main // class JTextDemo

101 8.2. TEKSTIKENTÄT 101 Esimerkissä 8.2 havainnollistetaan, miten luokasta JTextField voidaan johtaa luokka, johon ei mitenkään voi kirjoittaa pieniä kirjaimia tai oikeammin, voi kirjoittaa, mutta ne muuttuvat isoiksi kirjaimiksi. Esimerkissä 4.1 oli samantapainen tilanne, joka ratkaistiin matalantason tapahtumia suodattamalla nyt ratkaisuna on malliluokan (suoraviivainen) uudelleenmäärittely. Ainoastaan yksi malliluokan metodi pitää määritellä uudelleen. Tästä johtuen uuden tekstikomponentin UpperCase- Field kohdalla pitää määritellä uudelleen JTextComponent:sta peritty metodi createdefaultmodel. Huomaa vielä, miten uutta tekstikomponenttia käyttävässä appletissa määritellään dekoratiiviset koristukset tekstikenttäkomponenttien ympärille. Esimerkin ohjelman ulkoasu on kuvassa 8.2. Esimerkki 8.2 Tekstikenttäkomponentti, jonka sisällöksi ei voi tallettaa pieniä kirjaimia. import javax.swing. ; import javax.swing.text. ; import java.awt. ; public class UpperCaseDemo extends JApplet { public void init() { Container c = getcontentpane(); JTextField tf1 = new JTextField(20); tf1.setborder(borderfactory.createlineborder(color.green)); c.add(tf1, BorderLayout.NORTH); JTextField tf2 = new UpperCaseField(20); tf2.setborder(borderfactory.createlineborder(color.red)); c.add(tf2, BorderLayout.SOUTH); setsize(300,300); // init // UpperCaseDemo // JDK 1.3:n dokumentaatiosta class UpperCaseField extends JTextField { public UpperCaseField(int cols) { super(cols); protected Document createdefaultmodel() { return new UpperCaseDocument(); static class UpperCaseDocument extends PlainDocument { public void insertstring(int offs, String str, AttributeSet a) throws BadLocationException { if (str == null) return; char[] upper = str.tochararray(); for (int i = 0; i < upper.length; i++) upper[i] = Character.toUpperCase(upper[i]); super.insertstring(offs, new String(upper), a); // class UpperCaseDocument // class UpperCaseField

102 102 LUKU 8. SWING-KOMPONENTIT 8.3 Swingin nappulat Kuva 8.2: UpperCaseDemo suorituksessa. Swing-nappuloiden yliluokka AbstractButton määrittelee niiden yhteiset ominaisuudet. Kyseisellä luokalla on yli 70 metodia! AWT-nappuloiden tapaan Swing-nappuloiden merkitys on, että niihin voi liittää tapahtumankäsittelijän (tai useita), joka laukeaa nappulaa klikattaessa (hiirellä). Tekstikomponenttien tapaan nappuloilla on malliolio ja reunaviivat. Toisaalta leimojen tapaan sisältönä on tekstiä ja/tai ikoni. Nappuloihin liittyy enemmän tilanteita kuin leimoihin. Jokaista tilaa kohti voidaan asettaa erityinen ikoni: erityiset estetty -, valittu - sekä RollOver -ikonit (hiiri kulkee nappulan yli),.... Lisäksi on mahdollista asettaa nappulaan merkki, jonka painaminen näppäimistöltä aiheuttaa nappulan klikkauksen. Osa nappuloista on sellaisia, että niihin liittyy on/off-tila ja klikkaus vaihtaa nappulan tilaa tässä mielessä. Lisäksi erilaisiin nappuloihin liittyy erilaisia tapahtumankäsittelijöitä. JButton JCheckBox AbstractButton JToggleButton JRadioButton JMenuItem Kuva 8.3: Swingin nappulaluokkia. Swingin nappuloiden välistä perimyshierarkiaa on havainnollistettu kuvassa 8.3. Hierarkia on piirretty hieman epätavallisesti niin, että hierarkian juuri on vasemmassa reunassa (AbstractButton). Hierarkia ei ole täydellinen esimerkiksi luokalla JButton on useita aliluokkia. Nappuloiden ominaisuuksia on lyhyesti selitetty seuraavassa: JButton: Perusnappula, johon ei liity on/off-tilaa. Reagoi vain ActionEvent-tapahtumiin (nappulan klikkaus). JToggleButton: On/off-tyyppinen nappula: pohjassa tai ylhäällä. Painaminen tuottaa sekä ActionEventettä ItemEvent-tapahtumat. Yleensä tämän luokan instansseja ei luoda (vaan sen aliluokkien). JCheckBox: AWT:stä tuttu valintalaatikko. Klikkaus tuottaa ItemEvent tapahtuman. JRadioButton: Esittää radionappulaa, joka voi kuulua johonkin radionappularyhmään. Ryhmien luokka on ButtonGroup sillä on sama rooli kuin AWT:ssä. Swingin tapauksessa nap-

103 8.3. SWINGIN NAPPULAT 103 pulat lisätään ryhmään! Tapahtumat: ItemEvent. Edellisen tapaiset nappulat voidaan laittaa myös osaksi menua (luokan JMenuItem alta löytyy lisää nappuloita). Kuvassa 8.4 on esitetty sivulla 104 olevan esimerkin 8.3 ohjelman NappulaDemo ulkoasu. Huomaa, miten nappuloihin voi liittää ikonin ja/tai tekstiä. Ohjelmassa on kolme nappulaa ja yksi tekstikenttä. Nappuloita painamalla tapahtuu monenlaista, mutta erityisesti kahden ensimmäisen klikkaus kasvattaa tekstikentässä esitettävää lukuarvoa. Ensimmäisen klikkaus kasvattaa kahdella ja toisen klikkaus yhdellä. Lisäksi ensimmäiseen nappulaan on liitetty toisen nappulan ohjelmallinen klikkaus, joten jos toinen nappula ei ole estynyt -tilassa, niin ensimmäisen klikkaaminen kasvattaa lukuarvoa 2 + 1:llä. Kolmas nappula sotkee tilannetta niin, että sen klikkaaminen muuttaa toisen nappulan tilan estyneeksi / ei-estyneeksi. Kun ohjelman NappulaDemo koodia katsoo, niin huomaa, että kolmanteen nappulaan liitetty tapahtumankäsittelijä muuttaa myös kyseiseen nappulaan liittyvää vihjetekstiä. Huomaa myös, miten esimerkissä haetaan verkon yli WWW-palvelimelta GIF-kuvia ja tehdään niiden perusteella ikonikuvat. Edelleen kuvasta voi huomata, että nappuloihin on liitetty pikanäppäin toiminto ja vihjetekstit. Myös tekstikomponenttiin on liitetty vihjeteksti. Jos komponenttiin liittyy sekä vihjeteksti että pikanäppäintoiminto, niin pikanäppäimen koodi tulee automaattisesti osaksi vihjetekstiä (kuten kuvasta 8.4 on havaittavissa). Kuva 8.4: NappulaDemo suorituksessa.

104 104 LUKU 8. SWING-KOMPONENTIT Esimerkki 8.3 Erilaisia nappuloita ja niiden toiminnan havainnollistusta. import java.awt. ; import java.awt.event. ; import javax.swing. ; import java.net. ; public class NappulaDemo { private static String burst = " private static String at_work = " private static JButton b1, b2; private static JTextField tf = new JTextField("0"); private static JToggleButton tb; public static void main(string[] args) throws Exception { Icon i1 = new ImageIcon(new URL(burst)); Icon i2 = new ImageIcon(new URL(at_work)); JFrame f = new JFrame("Nappuloiden havainnollistusta"); Container c = f.getcontentpane(); c.setlayout(new FlowLayout()); b1 = new JButton(i1); b2 = new JButton("Lisää", i2); b1.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { b2.doclick(); lisää(2); ); b2.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { lisää(1); ); tb = new JToggleButton("Estä"); tb.additemlistener(new ItemListener(){ public void itemstatechanged(itemevent e){ if (tb.isselected()) { tb.settext("vapauta"); b2.setenabled(false); else { tb.settext("estä"); b2.setenabled(true); ); // Myös ActionListener toimii. tb.settooltiptext("estä/vapauta keskimmäinen"); b1.settooltiptext("lisää 2 + klikkaa keskimmäistä"); b2.settooltiptext("lisää yhdellä"); tf.settooltiptext("tätä lisätään nappuloilla"); b1.setmnemonic(keyevent.vk_a); b2.setmnemonic(keyevent.vk_l); tb.setmnemonic(keyevent.vk_k); c.add(b1); c.add(b2); c.add(tb); c.add(tf); f.pack(); f.setvisible(true); f.addwindowlistener(new IkkunanSulkija()); private static void lisää(int x) { try { tf.settext(integer.tostring(x + Integer.parseInt(tf.getText()))); catch (NumberFormatException e) { // lisää // class NappulaDemo

105 8.4. LIU UTTIMET Liu uttimet AWT:ssä muutamiin komponentteihin tuli reunalle liu uttimet automaattisesti, jos esitettävä komponentti oli suurempi kuin sille varattu tila. Swingin kohdalla tästä on pyritty eroon ja tarkoitus on, että käyttäjä lisää ylisuuret komponentit JScrollPane-tyyppiseen olioon. Ideana on luoda näkymä komponenttiin ikkunan kautta, kuten AWT:n ScrollPane:lla. Luokan JScrollPane avulla voi määrätä tuleeko vaaka- ja/tai pystysuuntainen liu utin alueen reunalle. Arvon valitsemista (kokonaislukuväliltä) varten Swingissä on myös kaksi luokkaa: JScrollBar ja JSlider. Näistä JScrollBar toimii kuten ScrollBar ja muutenkin niiden ulkoasut ovat samankaltaiset. Luokan JScrollBar-alkioilla on kuitenkin malliolio, joka on tyyppiä BoundedRangeModel. Jos liu uttimen arvoa muutetaan (AWT:n vastaavan komponentin tapaan), syntyy AdjustmentEventtyyppinen tapahtuma. Luokan JSlider mukaiset liu uttimet ovat hieman monipuolisempia ja näyttävämpiä, mutta niillä on sama toimintaidea kuin JScrollBar:llakin. Säädettävistä ominaisuuksista hyödyllisin lienee erilaisten väliasteikkojen asettaminen liu uttimen yhteyteen. Tähänkin luokkaan liittyy malliolio, joka on tyyppiä BoundedRangeModel. JSlider- ja JScrollBar-tyyppisten olioiden kesken on siis mahdollista jakaa malliolio. Jostain (kummasta) syystä JSlider tuottaa ChangeEvent-tyyppisiä tapahtumia. Kuva 8.5: SlideDemo suorituksessa. Kuvassa 8.5 on esitetty ohjelman SlideDemo ulkoasu (ohjelmakoodi on esimerkissä 8.4). Esimerkissä on neljä liu utinta ja niihin kuhunkin on liitetty tapahtumankäsittelijä, joka raportoi liu uttimen sen hetkisen arvon vastaavassa tekstikentässä. Esimerkkiin liittyvä erikoisuus on, että toinen ja neljäs liu utin jakavat saman malliolion keskenään kokeile esimerkkiä!

106 106 LUKU 8. SWING-KOMPONENTIT Esimerkki 8.4 Liu uttimien havainnollistamista. import java.awt. ; import java.awt.event. ; import javax.swing. ; import javax.swing.event. ; public class SlideDemo { private static JTextField a1,a2,b1, b2; private static JScrollBar sb1, sb2; private static JSlider js1, js2; public static void main(string[] args) { JFrame f = new JFrame("SlideDemo"); Container c = f.getcontentpane(); c.setlayout(new FlowLayout()); a1 = new JTextField(10); a2 = new JTextField(10); b1 = new JTextField(10); b2 = new JTextField(10); sb1 = new JScrollBar(JScrollBar.HORIZONTAL,10,8,0,100); sb2 = new JScrollBar(JScrollBar.HORIZONTAL,10,40,0,100); sb1.addadjustmentlistener(new AdjustmentListener(){ public void adjustmentvaluechanged(adjustmentevent e){ a1.settext("arvo: " + e.getvalue()); ); sb2.addadjustmentlistener(new AdjustmentListener(){ public void adjustmentvaluechanged(adjustmentevent e){ a2.settext("arvo: " + e.getvalue()); ); js1 = new JSlider(0, 100, 10); js2 = new JSlider(0, 100, 20); sb2.setmodel(js2.getmodel()); js1.setpaintlabels(true); js1.setpaintticks(true); js1.setmajortickspacing(20); js1.setminortickspacing(10); js1.addchangelistener(new ChangeListener(){ public void statechanged(changeevent e) { b1.settext("arvo: " + js1.getvalue()); ); js2.addchangelistener(new ChangeListener(){ public void statechanged(changeevent e) { b2.settext("arvo: " + js2.getvalue()); ); c.add(sb1); c.add(sb2); c.add(js1); c.add(js2); c.add(a1); c.add(a2); c.add(b1); c.add(b2); f.setsize(400,400); f.setvisible(true); f.addwindowlistener(new IkkunanSulkija()); // main // class SlideDemo

107 8.5. LISTAT Listat Swingissä valintalistoja voidaan toteuttaa luokilla JList ja JComboBox. Luokka JList on monivalintalista kuten vastaava luokka List AWT:ssä. Itse asiassa Swingissä ei ole suoraan AWT:n luokkaa Choice vastaavaa luokkaa. Luokan JComboBox merkitys on hyvin erilainen. Luokan JList olioiden ominaisuuksia ovat mm. seuraavat: joukko tekstialkioita, näkyvien lukumäärä ja alkukohta, reunalla ei ole liu uttimia, valitut alkiot, malliolio ja valintatila. Valintatiloja on useita. Voidaan valita yksi alkio, jokin yhtenäinen väli tai mielivaltaisesti poimia joitakin alkioita. Swingin yleisen periaatteen mukaan liu uttimet täytyy itse lisätä laittamalla JList tyyppinen komponentti JScrollPane:een! JList-tyyppiset oliot tuottavat ListSelectionEvent-tapahtumia, joita voi käsitellä ListSelectionListener:stä periytetyillä tapahtumankäsittelijöillä. Luokan JList alkioiden ei itse asiassa tarvitse olla tekstejä. Kuvien esittäminen listassa on myös mahdollista, mutta tuolloin pitää tehdä uusi listamalli ja esitysmoduuli (renderöinti). Luokka JComboBox on tavallinen pudotusvalikko (vrt. luokka Choice) täydennettynä mahdollisuudella kirjoittaa valinta vapaasti itse käyttäjän toimesta. Eli, JComboBox on tavallaan myös tekstikenttä. Pudotusvalikon alkiot ovat oletusarvoisesti tekstejä, mutta kuvia on myös mahdollista esittää tilanne on tältä osin sama kuin luokan JList kohdallakin. Valittaessa jokin pudotusvalikon alkio syntyy ItemEvent-tyyppinen tapahtuma. Luokat JList ja JComboBox käyttävät eri tyyppistä mallioliota. Kuva 8.6: JListatDemo suorituksessa. Kuva 8.6 esittää ohjelman JListatDemo (esimerkki 8.5) ulkoasua. JListatDemo havainnollistaa Swingin listoja: molempia listatyyppejä on kaksi. Kuhunkin listaan on kiinnitetty tapahtumankäsittelijä. Vasemmassa reunassa olevaan JList-tyyppiseen komponenttiin ja keskellä ylhäällä olevaan JComboBox-tyyppiseen komponenttiin kiinnitetyt tapahtumankäsittelijät raportoivat, ylhäällä olevaan tekstikenttään, tehdyistä valinnoista. Vastaavasti toiset kaksi listakomponettia raportoivat alareunassa olevaan tekstikomponenttiin. Huomaa, miten seteditable -metodia kutsumalla ohjelmassa asetetaan alempi JComboBox-komponentti sellaiseksi, että käyttäjä voi itse kirjoittaa valinnan, jos listassa sellaista ei ole. Reunoilla olevien listojen valintatilat ovat erilaiset kokeile usean arvon valitsemista oikeanpuoleisella listalla. Huomaa myös, mikä vaikutus on sillä, että oikeanpuoleiseen listaan ei ole liitetty liu utinta (alkioiden kuusi ja seitsemän valinta on nyt vaikeaa... ).

108 108 LUKU 8. SWING-KOMPONENTIT Esimerkki 8.5 Listojen havainnollistamista. import java.awt. ; import java.awt.event. ; import javax.swing. ; import javax.swing.event. ; public class JListatDemo { private static String[] lista = { "yksi", "kaksi", "kolme", "neljä", "viisi", "kuusi", "seitsemän" ; public static void main(string[] args) { JFrame f = new JFrame("Swing-listat: demo"); Container c = f.getcontentpane(); JList list1 = new JList(lista); JList list2 = new JList(lista); list1.setvisiblerowcount(4); list2.setvisiblerowcount(5); list1.setselectionmode(listselectionmodel.single_selection); list2.setselectionmode(listselectionmodel.multiple_interval_selection); JTextField tf1 = new JTextField(40); JTextField tf2 = new JTextField(40); list1.addlistselectionlistener(new Valitsija(tf1)); list2.addlistselectionlistener(new Valitsija(tf2)); c.add(list1, BorderLayout.EAST); c.add(new JScrollPane(list2), BorderLayout.WEST); c.add(tf1, BorderLayout.NORTH); c.add(tf2, BorderLayout.SOUTH); JComboBox cb1 = new JComboBox(lista); JComboBox cb2 = new JComboBox(lista); cb2.seteditable(true); JPanel p = new JPanel(); p.setlayout(new GridLayout(0,1)); p.add(cb1); p.add(cb2); c.add(p, BorderLayout.CENTER); cb1.additemlistener(new ComboValitsija(tf1)); cb2.additemlistener(new ComboValitsija(tf2)); f.pack(); f.setlocation(200,100); f.setvisible(true); f.addwindowlistener(new IkkunanSulkija()); // main static class Valitsija implements ListSelectionListener { private JTextField tf; public Valitsija(JTextField f) { tf = f; public void valuechanged(listselectionevent e) { JList list = (JList)e.getSource(); int[] inds = list.getselectedindices(); String valitut = "Valitut:"; ListModel model = list.getmodel(); for (int i=0; i<inds.length; i++) valitut += + (String)model.getElementAt(inds[i]); tf.settext(valitut); // valuechanged // class Valitsija static class ComboValitsija implements ItemListener { private JTextField tf; public ComboValitsija(JTextField f) { tf = f; public void itemstatechanged(itemevent e) { if (e.getstatechange() == ItemEvent.SELECTED) tf.settext("valittu: " + (String)e.getItem()); // class ComboValitsija // class JListatDemo

109 8.6. APULUOKKIA Apuluokkia Swingissä on myös useita pienehköjä GUI-komponetteja, jotka eivät mene mihinkään edellisistä kategorioista (ei myöskään jäljessä esitettäviin). Esimerkiksi luokka JProgressBar on kehitystilannetta kuvaava palkki tyyliin 55% ratkaistu. Palkki voi olla vaaka- tai pystysuuntainen, ja siihen voi liittyä tekstiä (ks. kuva 8.7) tai olla liittymättä. Tavallaan JProgressBar on hyvin samanlainen kuin JSlider ja JScrollBar, vaikka siihen ei liitykään käyttäjän mahdollisuutta siirtää palkin kehitystilannetta. Samanlaisuudesta johtuen JProgressBar:lla on samanlainen malliolio kuin liu uttimilla. Luokka JToolTip edustaa työkaluvihjetekstiä, jonka voi liittää mihin tahansa Swing-komponenttiin. Tätä luokkaa harvemmin kuitenkaan tarvitaan, sillä komponentteihin liittyy myös metodi set- ToolTipText (peritty JComponent:sta), jolla voi suoraan asettaa jonkin merkkijonon komponenttiin liittyväksi vihjetekstiksi. Jos komponenttiin on liitetty pikanäppäin, se näkyy automaattisesti myös vihjetekstissä. Luokan JSeparator mukaiset oliot toimivat pysty- tai vaakasuuntaisina erotinviivoina. Idea liittyy lähinnä sijoittelumanageriin FlowLayout, jossa voidaan vaakasuuntaisen eroitinviivan avulla pakottaa rivinvaihto (ks. kuva 8.7). AWT:n tapaan Swingissä on työkalu tiedoston nimen valitsemiseen: luokka JFileChooser. Se on hyvin samanlainen komponentti kuin luokka FileChooser. Edellisen lisäksi Swingiin liittyy värinvalintatyökalu: luokka JColorChooser. Esimerkissä 8.7 on ohjelma VariDemo (ulkoasu kuvassa 8.8), jossa nappulan painaminen aktivoi värinvalintatyökalun dialogi-ikkunana. Huomaa, miten dialogin kutsuminen palauttaa tuloksenaan valitun värin. Kuva 8.7: ProgressDemo suorituksessa. Ohjelma ProgressDemo esimerkissä 8.6 ja kuvassa 8.7 havainnollistaa eroitinviivoja (yksi pystysuuntainen ja kaksi vaakasuuntaista; eroittimien koko on määrätty niiden konstruktorin kutsun yhteydessä) ja JProgressBar:a. Ohjelmassa on kaksi palkkia, joista vain toisessa on tilaa kuvaava teksti mukana (huomaa, miten se asetetaan ohjelmassa setstringpainted -metodilla). Nappulaan on liitetty tapahtumankäsittelijä, jolla ensimmäisen palkin arvoa voi kasvattaa (myös pikanäppäintoiminnolla). Toiseen palkkiin on liitetty tapahtumankäsittelijä, joka reagoi palkin arvon muutoksiin ja raportoi ne alaosassa olevaan tekstikenttään. Huomaa myös, miten jälkimmäistä JProgressBar:a kasvatetaan GUI:n muodostavassa säikeessä se on selvä virheellisyys: kasvattaminen tulisi tehdä erillisessä säikeessä.

110 110 LUKU 8. SWING-KOMPONENTIT Esimerkki 8.6 JProgressBar:n sekä JSeparator:n havainnollistamista. import java.awt. ; import java.awt.event. ; import javax.swing. ; import javax.swing.event. ; public class ProgressDemo extends JFrame { public static void main(string[] args) { new ProgressDemo(); private JProgressBar pb1; private JTextField tf = new JTextField(10); public ProgressDemo() { super("jprogressbar etc demo"); setsize(500,500); Container c = getcontentpane(); c.setlayout(new FlowLayout()); pb1 = new JProgressBar(); JButton b = new JButton("Lisää"); b.setmnemonic(keyevent.vk_l); b.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e){ pb1.setvalue(pb1.getvalue()+10); ); JProgressBar pb2 = new JProgressBar(); pb2.setstringpainted(true); pb2.addchangelistener(new ChangeListener(){ public void statechanged(changeevent e){ JProgressBar pb = (JProgressBar)e.getSource(); tf.settext("arvo: " + pb.getvalue()); ); JSeparator sep1 = new JSeparator(JSeparator.VERTICAL); JSeparator sep2 = new JSeparator(); JSeparator sep3 = new JSeparator(); sep1.setpreferredsize(new Dimension(3,30)); sep2.setpreferredsize(new Dimension(400,3)); sep3.setpreferredsize(new Dimension(400,3)); c.add(b); c.add(sep1); c.add(pb1); c.add(sep2); c.add(pb2); c.add(sep3); c.add(tf); setvisible(true); addwindowlistener(new IkkunanSulkija()); for (int i=0; i<100; i++) { try { Thread.sleep(300); catch (InterruptedException e) { pb2.setvalue(i+1); // ProgressDemo() // class ProgressDemo

111 8.6. APULUOKKIA 111 Kuva 8.8: VariDemo suorituksessa. Esimerkki 8.7 Värinvalintatyökalun havainnollistus. import java.awt. ; import java.awt.event. ; import javax.swing. ; public class VariDemo extends JApplet { private JPanel panel = new JPanel(); public void init() { JButton b = new JButton("Aseta taustaväri"); b.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { panel.setbackground(jcolorchooser.showdialog (VariDemo.this, "Värin valinta", Color.red)); ); panel.add(b); getcontentpane().add(panel); // init // class VariDemo

112 112 LUKU 8. SWING-KOMPONENTIT 8.7 Layout-asiaa Swingiin liittyy useita uusia sijoittelumanagereita. Näistä mainittakoon BoxLayout ja OverlayLayout. Luokka BoxLayout esittää sijoittelumanageria, joka järjestää komponentit vaakariviin tai pystyriviin. OverlayLayout on sijoittelumanageri, joka sallii komponenttien menemisen toistensa päälle (osittain). Toinen tapahan tämän saavuttamiseksi on olla käyttämättä lainkaan sijoittelumanageria, mutta tuolloin jokaiselle komponentille pitää laskea kiinteä positio OverlayLayout on siis tietynlainen välimuoto kahden ääripään välillä. Esimerkki 8.8 Luokan JTabbedPane soveltamista. import java.awt. ; import javax.swing. ; import java.net. ; public class TabDemo { private static String forward = " private static String mail2 = " private static String redball = " private static String yellowball = " private static String greenball = " public static void main(string[] q) throws Exception { JFrame f = new JFrame("TabbedPane-demo"); JTabbedPane tp = new JTabbedPane(); JPanel p1, p2, p3; p1 = new JPanel(); p2 = new JPanel(); p3 = new JPanel(); p1.add(new JLabel("Label 1")); p2.add(new JLabel(new ImageIcon(new URL(forward)))); p3.add(new JButton(new ImageIcon(new URL(mail2)))); p1.setbackground(color.green); p2.setbackground(color.yellow); p3.setbackground(color.red); tp.addtab(null, new ImageIcon(new URL(greenball)), p1, "Eka"); tp.addtab(null, new ImageIcon(new URL(yellowball)), p2, "Toka"); tp.addtab(null, new ImageIcon(new URL(redball)), p3, "Kolmas"); f.getcontentpane().add(tp); f.setsize(300,200); f.setvisible(true); f.addwindowlistener(new IkkunanSulkija()); // main // class TabDemo Sijoittelumanagerien tapaista toimintaa tarjoavat myös luokat JTabbedPane ja JSplitPane. JTabbedPane on komponettisäiliö, johon yleensä laitetaan komponentteja sisältäviä säiliöitä korttipakkamaisesti. Vain yksi korteista on kerrallaan esillä ja kuhunkin korttiin liittyy kortistomainen valitsin (ks. kuva 8.9). Esimerkin 8.8 ohjelma TabDemo vain luo kolme korttia laittaen niihin kuhunkin yhden GUI-komponentin. Kuvassa vain viimeinen kortti on esillä. Toinen erikoisuus on luokka JSplitPane, joka on erikoinen kahden komponentin säiliö, jossa

113 8.7. LAYOUT-ASIAA 113 komponentit on sijoitettu joko päällekkäin tai vierekkäin, kiinni toisiinsa. Toisinaan tätä komponettia käytetään niin, että toisella ohjataan toista. Joustavasti toistensa suhteen venyviä rakenteita saa liimaamalla tällä komponentilla komponentteja toistensa suhteen (vaaka- tai pystysuuntaan). Esimerkissä 8.9 oleva ohjelma esittää yhtä WWW-sivua kahdessa komponentissa (WWW-sivu on tyhmästi koodattu kiinteästi ohjelmaan). Kuva 8.9: TabDemo suorituksessa. Esimerkki 8.9 Havainnollistus luokan JTabbedPane soveltamisesta. import javax.swing. ; import java.net. ; import java.awt. ; public class JSplitPaneDemo { public static void main(string[] q) throws Exception { JFrame f = new JFrame("JSplitPane esimerkki"); Container c = f.getcontentpane(); String dt = " JEditorPane ep1 = new JEditorPane(new URL(dt)); JScrollPane sp1 = new JScrollPane(ep1); sp1.setminimumsize(new Dimension(300,400)); JTextPane ep2 = new JTextPane(); ep2.setcontenttype(ep1.getcontenttype()); ep2.setdocument(ep1.getdocument()); // Sama kohde! JScrollPane sp2 = new JScrollPane(ep2); sp2.setminimumsize(new Dimension(400,200)); JSplitPane p = new JSplitPane(JSplitPane.VERTICAL_SPLIT, sp1, sp2); c.add(p); f.setsize(600,600); f.setvisible(true); f.addwindowlistener(new IkkunanSulkija()); // main // class JSplitPane

114 114 LUKU 8. SWING-KOMPONENTIT 8.8 Menupalkki Menupalkki on Swingissä hyvin samanlainen kuin AWT:ssäkin. Erona on lähinnä se, että Swingin valikkokomponentit ovat selkeämmin GUI-komponentteja kun AWT:ssä ne olivat pikemminkin erikoiskomponentteja. Toinen ero on, että Swingissä valikkoon voidaan liittää tapahtumankäsittelijä, joka reagoi valikon avautumiseen tai sulkeutumiseen (jne). Tätä varten on esitelty tapahtumankäsittelijä MenuListener. Tällaisten valikko-operaatioiden tarkoituksena on tehdä valikoista dynaamisempia: voidaan esimerkiksi sovelluksen tilan perusteella laittaa jotkin valikon kohdista estetyiksi tai ääritapauksena sovelluksen tilan perusteella voidaan koko valikon sisältö generoida aina avautumistapahtuman yhteydessä uudestaan. Luokka JMenu on valikko, joka sisältää valikkoalkioita tyyppiä JMenuItem ja eroittimina JSeparatorviivoja sekä luonnollisesti epäsuoraan (JMenuItem:n kautta) alivalikoita. JMenuItem:n vaihtoehdot ovat tekstialkio ja/tai ikoni, JMenu (alivalikko), JCheckBoxMenuItem ja JRadioButtonMenuItem. Atomiset valikkoalkiot (teksti ja/tai ikoni) ovat nappuloita! Valikon alkion valinta tuottaa joko tapahtuman ActionEvent tai ItemEvent. ActionEvent-tapahtuma syntyy tavallisen valikkoalkion kohdalla sekä rastitettaessa JCheckBoxMenuItem-alkio. ItemEventtapahtuma liittyy puolestaan JRadioButtonMenuItem-alkioon. Valikkoalkioihin voidaan liittää pikavalintoja sekä vihjeitä. Valikoita voidaan liittää menupalkkiin JMenuBar. AWT:stä poiketen tällainen komponetti voidaan laittaa kaikkiin raskaisiin ikkunakomponentteihin (JFrame, JWindow, JApplet ja JDialog). Kuva 8.10: JMenuDemo suorituksessa. Kuvassa 8.10 on havainnollistettu ohjelman JMenuDemo (esimerkki 8.10) käyttöliittymää. Sovelluksella on kolme valikkoa kaksi tavallista ja yksi help-valikko. Ensimmäisen valikon alivalikossa Lisää (ei käy kuvassa) on erityinen tapahtumankäsittelijä (tyyppiä AvausKuuntelija ), joka valikon avautumisen yhteydessä arpoo jokaisen alivalikon komponentin kohdalla, onko se jatkossa estetty -tilassa vai ei kokeile tätä toimintaa! Ensimmäisessä valikossa ja sen alivalikossa on yhteensä neljä erilaista väripalloa. Kuhunkin väripalloon (oikeammin valikon kohtaan) on kiinnitetty tapahtumankäsittelijä (metodin teevärivalikko keskivaiheilla), joka reagoi valintaan vaihtamalla kehyksen taustavärin. Toista valikkoa on havainnollistettu kuvassa Siihen ei ole laitettu tapahtumankäsittelijöitä, vaan ideana on pikemminkin ollut esitellä erilaisia valikkokomponentteja ja pikanäppäinten liittämistä valikon alkioihin. Huomaa, miten tavallinen radionappula ja valikossa olevat radionappulat kuuluvat

115 8.8. MENUPALKKI 115 samaan ryhmään. Esimerkkiin liittyy vielä help-valikko, jonka sisältö ei ole mitenkään erityinen. Hassua tämän valikon kohdalla on, että sitä ei edes JDK 1.5:n yhteydessä voi lisätä sethelpmenu -metodilla, vaikka niin voisi kuvitella, sillä kyseisen metodin toiminnallisuutta ei ole oikealla tavalla toteutettu kokeile asiaa! Esimerkissä se on lisätty menupalkkiin toisin sillä seurauksella, että valikolla ei ole nyt nimeä, eikä se siten näy edes kuvassa 8.10, vaikka se onkin välittömästi kahden edellisen jälkeen ja sen voi jopa avata sopivasta kohdasta klikkaamalla. Esimerkki 8.10 Valikoiden havainnollistamista 1/2. import java.awt. ; import java.awt.event. ; import javax.swing. ; import javax.swing.event. ; import java.net. ; public class JMenuDemo { private static ImageIcon redball, greenball, yellowball, blueball; private static ButtonGroup ryhmä = new ButtonGroup(); private static void luoväripallot() throws Exception { String base = " redball = new ImageIcon(new URL(base+"redball.gif")); blueball = new ImageIcon(new URL(base+"blueball.gif")); greenball = new ImageIcon(new URL(base+"greenball.gif")); yellowball = new ImageIcon(new URL(base+"yellowball.gif")); public static void main(string[] q) throws Exception { JFrame f = new JFrame("JMenuDemo"); Container c = f.getcontentpane(); c.setlayout(new FlowLayout()); luoväripallot(); f.addwindowlistener(new IkkunanSulkija()); JMenuBar palkki = new JMenuBar(); palkki.add(teevärivalikko(c)); palkki.add(teerastitusvalikko()); // palkki.sethelpmenu(teeopastusvalikko()); palkki.add(teeopastusvalikko()); JRadioButton r = new JRadioButton("Värivalinta"); ryhmä.add(r); c.add(r); f.setjmenubar(palkki); f.setsize(500,300); f.setvisible(true); // main

116 116 LUKU 8. SWING-KOMPONENTIT Esimerkki Valikoiden havainnollistamista (jatkoa 2/2). static JMenu teevärivalikko(container c) { JMenu menu = new JMenu("Väri"); JMenuItem i1, i2, i3, i4; i1 = new JMenuItem("Punainen", redball); i2 = new JMenuItem("Sininen", blueball); i3 = new JMenuItem(yellowBall); i4 = new JMenuItem(greenBall); i1.setmnemonic( P ); i3.setmnemonic( K ); i1.addactionlistener(new MenuKuuntelija(c, Color.red)); i2.addactionlistener(new MenuKuuntelija(c, Color.blue)); i3.addactionlistener(new MenuKuuntelija(c, Color.yellow)); i4.addactionlistener(new MenuKuuntelija(c, Color.green)); JMenu m3 = new JMenu("Lisää"); m3.addmenulistener(new AvausKuuntelija(m3)); m3.add(i3); m3.add(i4); menu.add(i1); menu.add(i2); menu.addseparator(); menu.add(m3); menu.setmnemonic( V ); return menu; // teevärivalikko static JMenu teerastitusvalikko() { JMenu menu = new JMenu("Rastitusta"); menu.add(new JCheckBoxMenuItem("Musta", true)); menu.add(new JCheckBoxMenuItem("Punainen", redball)); menu.add(new JCheckBoxMenuItem(greenBall)); menu.add(new JCheckBoxMenuItem("Sininen", blueball, true)); menu.addseparator(); JRadioButtonMenuItem r1,r2,r3; r1 = new JRadioButtonMenuItem("Keltainen"); r2 = new JRadioButtonMenuItem(yellowBall); r3 = new JRadioButtonMenuItem("Sininen", blueball); menu.add(r1); menu.add(r2); menu.add(r3); ryhmä.add(r1); ryhmä.add(r2); ryhmä.add(r3); r3.setmnemonic( S ); menu.setmnemonic( R ); return menu; // teerastitusvalikko static JMenu teeopastusvalikko() { JMenu menu = new JMenu(); menu.add(new JMenuItem("Help", H )); menu.addseparator(); menu.add(new JMenuItem("About this tool")); menu.setmnemonic( H ); return menu; // teeopastusvalikko static class AvausKuuntelija implements MenuListener { private JMenu menu; AvausKuuntelija(JMenu m) { menu = m; public void menucanceled(menuevent e) { public void menudeselected(menuevent e) { public void menuselected(menuevent e) { menu.getitem(0).setenabled(math.random()<0.5); menu.getitem(1).setenabled(math.random()<0.5); // AvausKuuntelija static class MenuKuuntelija implements ActionListener { private Color väri; private Container säiliö; public MenuKuuntelija(Container c1, Color c2) { säiliö = c1; väri = c2; public void actionperformed(actionevent e) { säiliö.setbackground(väri); // MenuKuuntelija // JMenuDemo

117 8.8. MENUPALKKI Luokka JToolBar AWT:stä poiketen Swingin yhteydessä päätason ikkunakomponentteihin voidaan liittää erillinen työkalupalkki: luokan JToolBar olio. Sillä ei ole menupalkin tapaan kiinteää paikkaa, vaan se voi kiinnittyä vaakareunaan tai pystyreunaan. Käyttämällä työkalupalkin lisäämisen yhteydessä arvoa BorderLayout.NORTH, saadaan työkalupalkki lisätty yläreunaan. Palkki voi siis olla vaaka- tai pystysuuntainen. Työkalupalkkiin voidaan lisätä sellaisia alkioita, jotka toteuttavat Action-rajapinnan. Erityisesti nappulat toteuttavat tämän kyseisen rajapinnan, joten tyypillisesti työkalupalkkiin laitetaan JButton-alkioita, joiden ainoa sisältö on ikonikuva. Työkalupalkkia voidaan raahata! Kokeile esimerkissä 8.11 luodun työkalupalkin raahaamista ja kiinnittämistä vasempaan pystyreunaan. Sen voi itse asiassa myös pudottaa koko ikkunan ulkopuolelle, jolloin siitä tulee pieni ikkuna. Tällainen ns. Drag-and-Drop on mahdollista yleisemminkin, mutta sitä ei käsitellä tämän oppimateriaalin puitteissa (ks. java.awt.dnd-pakettia). Kuva 8.11: JToolBarDemo suorituksessa. Esimerkin 8.11 ohjelmalla JToolBarDemo on sekä menu- että työkalupalkki. Kummallakin voi vaihtaa taustan väriä. Palkkia voi myös raahata. Alkuperäinen tilanne on esitetty kuvassa 8.11 kun työkalupalkista on otettu kiinni ja se on vapaasti pudotettu, on saatu aikaan kuvan 8.12 tilanne. Kuva 8.12: JToolBarDemo värin vaihtamisen ja palkin raahaamisen jälkeen.

118 118 LUKU 8. SWING-KOMPONENTIT Esimerkki 8.11 Työkalupalkin havainnollistamista. import java.awt. ; import java.awt.event. ; import javax.swing. ; import javax.swing.event. ; import java.net. ; public class JToolBarDemo { private static ImageIcon redball, blueball, greenball, yellowball; private static void luoväripallot() throws Exception { String base = " redball = new ImageIcon(new URL(base+"redball.gif")); blueball = new ImageIcon(new URL(base+"blueball.gif")); greenball = new ImageIcon(new URL(base+"greenball.gif")); yellowball = new ImageIcon(new URL(base+"yellowball.gif")); public static void main(string[] q) throws Exception { JFrame f = new JFrame("JToolBarDemo"); Container c = f.getcontentpane(); luoväripallot(); f.addwindowlistener(new IkkunanSulkija()); JPanel p = new JPanel(); p.setsize(400,300); c.add(p); // CENTER JMenuBar palkki = new JMenuBar(); palkki.add(teevärivalikko(p)); c.add(teetyökalupalkki(p),borderlayout.north); f.setjmenubar(palkki); f.setsize(500,300); f.setvisible(true); // main static JMenu teevärivalikko(container c) { JMenu menu = new JMenu("Väri"); JMenuItem i1, i2, i3, i4; i1 = new JMenuItem("Punainen", redball); i2 = new JMenuItem("Sininen", blueball); i3 = new JMenuItem(yellowBall); i4 = new JMenuItem(greenBall); i1.setmnemonic( P ); i3.setmnemonic( K ); i1.addactionlistener(new ActionKuuntelija(c, Color.red)); i2.addactionlistener(new ActionKuuntelija(c, Color.blue)); i3.addactionlistener(new ActionKuuntelija(c, Color.yellow)); i4.addactionlistener(new ActionKuuntelija(c, Color.green)); menu.add(i1); menu.add(i2); menu.addseparator(); menu.add(i3); menu.add(i4); menu.setmnemonic( V ); return menu; // teevärivalikko static JToolBar teetyökalupalkki(container c) { JToolBar tb = new JToolBar(JToolBar.HORIZONTAL); JButton b = new JButton(redBall); b.addactionlistener(new ActionKuuntelija(c, Color.red)); tb.add(b); b = new JButton(blueBall); b.addactionlistener(new ActionKuuntelija(c, Color.blue)); tb.add(b); b = new JButton("Green", greenball); b.addactionlistener(new ActionKuuntelija(c, Color.green)); tb.add(b); b = new JButton(yellowBall); b.settooltiptext("keltainen toimenpide..."); b.addactionlistener(new ActionKuuntelija(c, Color.yellow)); tb.add(b); return tb; // teetyökalupalkki static class ActionKuuntelija implements ActionListener { private Color väri; private Container säiliö; public ActionKuuntelija(Container c1, Color c2) { säiliö = c1; väri = c2; public void actionperformed(actionevent e) { säiliö.setbackground(väri); // ActionKuuntelija // JMenuDemo

119 8.9. DIALOGI-IKKUNAT Dialogi-ikkunat Swingissä on dialogeja varten luokka JDialog. Se on hyvin samanlainen kuin Dialog, dialogit ovat joko modaalisia tai ei-modaalisia. AWT:stä poiketen JDialog ei tarvitse omistajaikkunaa. Aiemmin jo tullut todetuksikin, että dialogeihin voi nyt liittyä menupalkki. Luokan JDialog käytön tarvetta on Swingissä vähennetty apuluokan JOptionPane avulla. Se sisältää joukon staattisia metodeja, joiden avulla voidaan luoda tietynlaisia tavallisesti tarvittavia dialogi-ikkunoita. Metodilla showconfirmdialog luodaan yes/no/cancel-tyyppinen dialogi-ikkuna. Vastaavasti metodilla showinputdialog voi kysyä käyttäjältä tekstimuotoista vastausta johonkin yksinkertaiseen kysymykseen. Metodia showmessagedialog käytetään monipuolisten tiedoitusdialogien esittämiseen. Lopuksi, metodilla showoptiondialog voidaan pyytää käyttäjää valitsemaan jokin useista vaihtoehdoista. Esimerkki 8.12 Dialogi-ikkunoiden tuottamista luokalla JOptionPane. import java.awt. ; import java.awt.event. ; import javax.swing. ; import java.util. ; public class OptionPaneDemo extends JApplet { private ImageIcon kuva = new ImageIcon("burst.gif"); private JTextField tulos = new JTextField("Aluksi"); public void init() { JButton b = new JButton("Paina"); Container c = getcontentpane(); c.setlayout(new FlowLayout()); c.add(tulos); c.add(b); b.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { JOptionPane.showMessageDialog( OptionPaneDemo.this, new Date().toString(), "Kello on nyt", JOptionPane.INFORMATION_MESSAGE, kuva); String v = JOptionPane.showInputDialog("Keskiviikko?"); tulos.settext(v); int val = JOptionPane.showConfirmDialog( OptionPaneDemo.this, "Tiedätkö tämän asian?"); tulos.settext(integer.tostring(val)); ); setsize(300,200); // init // class OptionPaneDemo Esimerkin 8.12 ohjelmassa OptionPaneDemo luodaan erilaisia dialogi-ikkunoita appletissa yksi toisensa perään. Alkutilannetta ja syntyviä dialogi-ikkunoita on havainnollistettu kuvissa

120 120 LUKU 8. SWING-KOMPONENTIT Kuva 8.13: OptionPaneDemo suorituksessa: vaihe 1. Kuva 8.14: OptionPaneDemo suorituksessa: vaihe 2. Kuva 8.15: OptionPaneDemo suorituksessa: vaihe 3. Kuva 8.16: OptionPaneDemo suorituksessa: vaihe 4.

121 8.10. TAULUKOT LUOKALLA JTABLE Taulukot luokalla JTable Swingissä on AWT:hen nähden täysin uudenlainen GUI-komponentti 2-ulotteisen taulukkomuotoisen data esittämiseksi ja editoimiseksi. Tämä komponentti on JTable. Tällä taulukkokomponentilla on yhteys relaatiotietokantojen tauluihin mm. siinä mielessä, että sarakkeille voidaan asettaa otsikkoteksti. Taulukkokomponentti on monessa mielessä hyvin dynaaminen. Sisällön esittämisvaihtoehtojen muodostamisen ja solujen editoimisen lisäksi sen kokoa voidaan myös muuttaa. Taulukon alkiot voivat olla esim. lukuja, tekstiä, kuvia,.... Yleinen ajatus on, että kullakin rivillä on kunkin sarakkeen kohdalla samantyyppistä tietoa, mutta rivin eri sarakkeilla voi luonnollisesti olla erityyppistä tietoa. Taulukolla on oma malliolionsa, joka on tyyppiä TableModel. Edellinen on tosin vain rajapinta, jolle Swingistä löytyy toteutuksia: AbstractTableModel ja DefaultTableModel. Taulukkokomponentin käyttäminen usein perustuu siihen, että määritellään itse komponentille malli, joka yleensä tehdään periyttämällä jommasta kummasta edellä mainitusta toteutuksesta. Esimerkiksi, jos halutaan esittää eksoottista tietoa taulukossa tai vaikkapa rajoittaa editointitoimenpiteitä jollakin tavalla joudutaan usein määrittelemään uusi malli. Taulukkokomponentti poikkeaa muista GUIkomponenteista myös siinä mielessä, että vaikka siihen (ja sen malliolioon liittyy tapahtumia), niin koko GUI-komponentin toiminnallisuus saadaan lähinnä aikaan malliolion toiminnallisuuden avulla. Uuden luokan periyttäminen malliluokasta on taulukkokomponentin yhteydessä siis hyvin tavallista. Mallin määrittelemisen lisäksi joudutaan eksoottisemmissa tapauksissa toteuttamaan uudestaan myös sisällön esittämisestä (renderöinnistä) huolehtiva taulukkokomponentin osa. Tällaista renderöinnin määrittelemistä ei tämän materiaalin puitteissa käsitellä. (On Java-kirjoja, jotka käsittelevät taulukkokomponenttia 100 sivun verran tarkastelematta renderöintiä... ) JTable-olion ulkoasua voidaan käsitellä myös niin, että suorituksen aikana sarakkeisiin voidaan (työkalupalkin tapaan) soveltaa raahaamista. Raahaaminen tapahtuu vain GUI:ssa, sillä ei ole vaikutusta malliolion sisältöön. Yksinkertaisimmillaan taulukkokomponentin käytön yhteydessä ei itse tarvitse määritellä juuri mitään. Laajimmillaan määritellään esimerkiksi alkioiden haku, talletus, renderöinti ja editointi. Malliolion kautta taulukkokomponentti on erittäin näppärä ajatellen GUI-ohjelmassa käsiteltävää tietoa taulukkokomponentin sisällön editointi GUI:ssa voidaan helposti tehdä sellaiseksi, että muutokset kohdistuvat suoraan ohjelmassa olevaan 2-ulotteiseen taulukko-olioon. Luokan JTable metodeita sen enempää kuin malliluokkien metodeitakaan ei tässä esitellä. Näiltä osin kehoitetaan tutkimaan JDK:n dokumentaatiota. Ylipäänsä JTable:n käyttö kaikessa laajuudessaan on niin monimutkaista, että seuraavassa tyydytään selvittämään taulukkokomponentin käyttöä kolmen esimerkin kautta. Kuvassa 8.17 esitetään ohjelman TableDemoI ulkoasu (esimerkki 8.13). Kyseisen ohjelman suorituksen seurauksena syntyy taulukkokomponentti, jonka solujen sisältöä voi editoida vapaasti. Kokeile editointia ja aiemmin mainittua sarakkeiden raahaamista. Huomaa, että esimerkissä on asetettu sarakkeille otsikot oletusarvoisesti otsikot ovat A, B, C,.... Kaikki taulukon rivit eivät ole mahtuneet esille taulukkokomponentti on tietoisesti laitettu main -metodissa JScrollPane-olion sisään. Esimerkkiin on liitetty nappula, jonka painamisen seurauksena (tapahtumankäsittelijän avulla) lasketaan pääohjelmassa luodun 2-ulotteisen Integer-tyyppisiä lukuja sisältävän taulukon lukujen summa. Huomaa, miten main -metodissa luodaan JTable-olio luomalla sen malliolio käyttämällä itseluotua 2-ulotteista taulukko-oliota luvut. Voisi ajatella, että solujen arvon editoiminen vaikuttaisi myös taulukko-olion luvut sisältöön. Näin ei kuitenkaan tapahdu.

122 122 LUKU 8. SWING-KOMPONENTIT Esimerkki 8.13 JTable:n käyttöä. import javax.swing. ; import java.net. ; import java.awt. ; import java.awt.event. ; import javax.swing.table. ; public class TableDemoI { public static void main(string[] args) { JFrame f = new JFrame("JTable havainnollistus I"); Container c = f.getcontentpane(); c.setlayout(new FlowLayout()); final Integer[][] luvut = new Integer[30][5]; for (int i=0; i<30; i++) for (int j=0; j<5;j++) luvut[i][j] = new Integer(i+j); String[] otsikot = {"N1", "N2", "N3", "N4", "N5" ; JTable table = new JTable(new DefaultTableModel(luvut,otsikot)); c.add(new JScrollPane(table)); final JTextField tf = new JTextField(); JButton b = new JButton("Summaa luvut"); c.add(b); c.add(tf); b.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { tf.settext( + summaa(luvut)); ); f.setsize(600,600); f.setvisible(true); f.addwindowlistener(new IkkunanSulkija()); // main private static int summaa(integer[][] t) { int summa = 0; for (int i=0; i<t.length; i++) for (int j=0; j<t[i].length; j++) summa += t[i][j].intvalue(); return summa; // summaa // class TableDemoI Esimerkissä 8.14 esitetään ohjelma TableDemoII (ulkoasu on kuvassa 8.18), joka on ohjelman TableDemoI kaltainen. Nyt jos solujen arvona olevia lukuja muutetaan, niin muutokset menevät välittömästi malliolion pohjana olevaan 2-ulotteiseen kokonaislukutaulukkoon, ja nappulaan liitetty lukujen summaus toimii oikein! Esimerkin parempi toiminnallisuus johtuu siitä, että malliluokasta AbstractTableModel on periytetty uusi malliluokka, jota käytetään main -metodissa luotavan JTableolion mallina. Toimivuus ohjelmaan TableDemoI nähden perustuu oikeastaan metodiin setvalueat, jolla ei ole vastaavan sisältöistä toteutusta luokassa AbstractTableModel. Esimerkissä luodun malliluokan IntegerTableModel kaikki metodit (konstruktoria lukuunottamatta) on peritty yliluokasta ja

123 8.10. TAULUKOT LUOKALLA JTABLE 123 Kuva 8.17: TableDemoI suorituksessa. Summaus ei toimi! tässä luokassa niille annetaan uusi toteutus. Olennaista on tietysti myös se, että uusi malliluokka käyttää (jakamalla) sille konstruktorin kautta välitettävää 2-ulotteista kokonaislukutaulukkoa. Metodien getrowcount ja getcolumncount merkitys on suoraviivainen: mallia käyttävä GUI-komponentti tarvitsee tiedon näistä. Metodin iscelleditable merkitys on kertoa GUI-komponentille, voiko solun sisältöä editoida. Metodilla getvalueat GUI-komponentti hakee mallioliolta solun sisällön ja muuttuneen sisällön se puolestaan taltioi metodilla setvalueat.

124 124 LUKU 8. SWING-KOMPONENTIT Kuva 8.18: TableDemoII suorituksessa. Summaus toimii!! Lopuksi huomaa, että malliluokan toteutuksen ei tarvitse perustua 2-ulotteiseen taulukko-olioon. Riittää kun malliolio (luokassa IntegerTableModel toteutettujen metodien tapaan) näyttää ulospäin 2-ulotteiselta taulukolta, jonka alkioita voidaan havainnoida ja asettaa. Pääohjelmassa JTable-olio luodaan hieman eri tavalla kuin ohjelmassa TableDemoI, mutta muutoin pääohjelmassa ei pitäisi olla mitään erityistä.

125 8.10. TAULUKOT LUOKALLA JTABLE 125 Esimerkki 8.14 Luokan JTable käyttämistä määrittelemällä itse uusi malliluokka. import javax.swing. ; import java.net. ; import java.awt. ; import java.awt.event. ; import javax.swing.table. ; public class TableDemoII { public static void main(string[] args) { JFrame f = new JFrame("JTable havainnollistus II"); Container c = f.getcontentpane(); c.setlayout(new FlowLayout()); final int[][] luvut = new int[30][5]; for (int i=0; i<30; i++) for (int j=0; j<5;j++) luvut[i][j] = i+j; JTable table = new JTable(new IntegerTableModel(luvut)); c.add(new JScrollPane(table)); final JTextField tf = new JTextField(); JButton b = new JButton("Summaa luvut"); c.add(b); c.add(tf); b.addactionlistener(new ActionListener(){ public void actionperformed(actionevent e) { tf.settext( + summaa(luvut)); ); f.setsize(600,600); f.setvisible(true); f.addwindowlistener(new IkkunanSulkija()); // main private static int summaa(int[][] t) { int summa = 0; for (int i=0; i<t.length; i++) for (int j=0; j<t[i].length; j++) summa += t[i][j]; return summa; // summaa // class TableDemoII class IntegerTableModel extends AbstractTableModel { private int[][] luvut; IntegerTableModel(int[][] l) { luvut = l; public int getrowcount() { return luvut.length; public int getcolumncount() { return luvut[0].length; public boolean iscelleditable(int row, int col) { try { int v = luvut[row][col]; catch (Exception e) { return false; return true; // iscelleditable public Object getvalueat(int row, int col) { try { return new Integer(luvut[row][col]); catch (Exception e) { return null; // getvalueat public void setvalueat(object v, int row, int col) { try { luvut[row][col] = Integer.parseInt(v.toString().trim()); catch (Exception e) { // setvalueat // IntegerTableModel

126 126 LUKU 8. SWING-KOMPONENTIT Esimerkki 8.15 Taulukon alkioina muutakin kuin lukuja. import javax.swing. ; import java.net. ; import java.awt. ; import javax.swing.table. ; public class TableDemo { public static void main(string[] q) throws Exception { JFrame f = new JFrame("JTable havainnollistus"); Container c = f.getcontentpane(); Object[][] t = teetiedot(); String[] otsikot = {"Kuva", "Nimi", "Puhtaus", "Koodi"; JTable table = new JTable(new OmaTableModel(t,otsikot)); c.add(new JScrollPane(table)); f.setsize(400,400); f.setvisible(true); f.addwindowlistener(new IkkunanSulkija()); // main static Object[][] teetiedot() throws Exception { String base = " String[] nimet = { "red", "blue", "green", "yellow", "pink" ; Object[][] rivit = new Object[5][]; for (int i=0; i<5; i++) { ImageIcon ii = new ImageIcon(new URL(base+nimet[i]+"ball.gif")); Object[] r = { ii, nimet[i], new Boolean(false), new Character(nimet[i].charAt(0)) ; rivit[i] = r; return rivit; // teetiedot // class TableDemo class OmaTableModel extends DefaultTableModel { private static final int NIMI = 1, KUVA = 0, PUHTAUS = 2, KOODI = 3, COLS = 4; OmaTableModel(Object[][] t, String[] o) { super(t,o); public Class getcolumnclass(int col) { // Mahdollistaa renderöinnin. switch (col) { case NIMI: return String.class; case KUVA: return ImageIcon.class; case PUHTAUS: return Boolean.class; case KOODI: return Character.class; default: return super.getcolumnclass(col); // getcolumnclass public boolean iscelleditable(int row, int col) { if (col == KOODI) return false; return ((0 col) & (col < COLS) & (0 row) & (row getrowcount())); // iscelleditable // OmaTableModel

127 8.11. PUURAKENTEET LUOKALLA JTREE 127 Kolmas esimerkki 8.15 esittelee ohjelman TableDemo, joka ulkoasu näkyy kuvasta Edellisistä esimerkeistä poiketen, nyt halutaan hieman havainnollistaa taulukkokomponentin kykyä esittää erilaista dataa havainnollisesti. Jos ohjelmaa TableDemo ja kuvaa vertaa tarkkaan, niin voi havaita, että ohjelman suorituksen aikana taulukkokomponentin kolmas sarake on raahattu toisen paikalle. Kuva 8.19: TableDemo suorituksessa. Esimerkkiohjelmassa TableDemo luodaan oma malliluokka periyttämällä luokasta DefaultTable- Model. Tällä kertaa ei malliluokkaan rakenneta taulukon editoinnin mahdollistamista, joten vaikka esimerkiksi ikonikuvan takana olevaa URL-osoitetta voi mennä muuttamaan, niin sillä tavalla ei saa aikaan uutta ikonia tällainen editoitavuus olisi kylläkin voitu toteuttaa antamalla uusi toteutus metodille setvalueat. Solujen editoitavuuden osalta on määritelty, että Koodi -sarakkeen arvoja ei saa edes GUI:ssa mennä muuttamaan (kokeile!). Muutoin esimerkin tarkoituksena on lähinnä havainnollistaa, miten toteuttamalla metodi getcolumnclass saadaan aikaan esimerkiksi ImageIcontyyppisten arvojen näkyminen ikonina myös taulukossa. Samoin Boolean-tyyppiset arvot näkyvät rastituslaatikkoina Puurakenteet luokalla JTree Tässä luvussa luodaan pikainen katsaus hyvin monipuoliseen GUI-komponenttiin, jolla voidaan esittää ja editoida hierarkisia puurakenteita. Kyseinen luokka on Swingin JTree. Luokan JTable tapaan tämänkin luokan toiminnassa päähuomio on mallioliossa eikä niinkään tapahtumissa. Edelleen JTable:n tapaan seuraavassa pyritään lähinnä antamaan (esimerkkien avulla) vaikutelma siitä, mitä komponentilla voi tehdä yksityiskohtia ei käydä systemaattisesti läpi. Perusajatus JTree-luokan takana on tiedostohierarkian esittäminen. Toteutuksen takana olevat ideat ovat hyvin samanlaisia kuin JTable:ssa. Luokan JTree idea on vain esittää puuta. Tietosisällön malliolio on tyyppiä TreeModel (rajapinta). Hierarkian esitys on vertikaalista (vrt. Windows). Jokainen rivi sisältää yhden alkion, joka on juuri, lehtisolmu tai sisäsolmu. Puun alipuu voi olla avattuna (displayed) tai suljettuna (collapsed). Edellinen on luonnollisesti sisäsolmujen ja juuren ominaisuus. Rivillä esitetään oletusarvoisesti tekstiä, mutta voidaan esittää muutakin, esim. kuvia. Renderöintiä varten on luokkia, jotka eksoottisemmassa tapauksessa pitää määritellä uudelleen (ei käsitellä). Eri Look&Feel-luokat esittävät puun symboleja hieman eri tavoin (Windows / Motif /... -ulkoasu ks. kuvia 8.20 ja 8.21). Puuolio voidaan tehdä monella tapaa: suoraan tietynlaisesta solmurakenteesta, mallioliosta, jne. Solmurakenteen tapauksessa solmut ovat tyyppiä TreeNode (rajapinta), MutableTreeNode (rajapinta) tai DefaultMutableTreeNode. Puukomponentin yhteydessä käytettävän malliluokan TreeModel

128 128 LUKU 8. SWING-KOMPONENTIT Kuva 8.20: PuuEsitys suorituksessa (windows). (rajapinta) toteuttaa konkreettisesti luokka DefaultTreeModel. Osa JTree-luokkaan liittyvistä luokista on paketissa javax.swing.tree. Kuva 8.21: PuuEsitys suorituksessa (motif). Malliolioon kohdistuneet muutokset tuottavat tapahtuman, joten toiminnallisuuden voi rakentaa kiinnittämällä puuolioon tapahtumankäsittelijöitä eri toimenpiteitä varten: valinta, poisto, lisäys, avaaminen,.... Toisaalta taulukkokomponentin tapaan toiminnallisuus voidaan myös muodostaa johtamalla uusi tarkoituksen mukainen malliluokka puuoliolle.

129 8.11. PUURAKENTEET LUOKALLA JTREE 129 Esimerkki 8.16 Vakiomuotoisen puukomponentin rakentaminen. import javax.swing. ; import javax.swing.tree. ; public class PuuEsitys { public static void main(string[] args) { JFrame kehys = new JFrame("PuuEsitys - demo"); kehys.setsize(300,200); kehys.addwindowlistener(new IkkunanSulkija()); // Puurakenne DefaultMutableTreeNode juuri, lintu, nisäkäs, matelija, pöllö, haukka, ruskosuohaukka, hiirihaukka, hiiripöllö, tunturipöllö; juuri = new DefaultMutableTreeNode("Eläin"); matelija = new DefaultMutableTreeNode("Matelija"); lintu = new DefaultMutableTreeNode("Lintu"); nisäkäs = new DefaultMutableTreeNode("Nisäkäs"); pöllö = new DefaultMutableTreeNode("Pöllö"); haukka = new DefaultMutableTreeNode("Haukka"); ruskosuohaukka = new DefaultMutableTreeNode("Ruskosuohaukka"); hiirihaukka = new DefaultMutableTreeNode("Hiirihaukka"); hiiripöllö = new DefaultMutableTreeNode("Hiiripöllö"); tunturipöllö = new DefaultMutableTreeNode("Tunturipöllö"); juuri.add(matelija); juuri.add(lintu); juuri.add(nisäkäs); lintu.add(pöllö); lintu.add(haukka); haukka.add(ruskosuohaukka); haukka.add(hiirihaukka); pöllö.add(hiiripöllö); pöllö.add(tunturipöllö); // Puuolio JTree puu = new JTree(juuri); kehys.getcontentpane().add(new JScrollPane(puu)); try { UIManager.setLookAndFeel("com.sun.java.swing.plaf.motif.MotifLookAndFeel"); // UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); SwingUtilities.updateComponentTreeUI(kehys); catch (Exception e) { kehys.setvisible(true); // main // class PuuEsitys

130 130 LUKU 8. SWING-KOMPONENTIT Esimerkin 8.16 ohjelmalla PuuEsitys (jota on havainnollistettu kuvissa 8.20 ja 8.21) tuotetaan vakiomuotoinen puuolio. Esimerkin ideana on lähinnä havainnollistaa, miten solmurakenne voidaan muodostaa, ja miten solmurakenteen juuren avulla voidaan rakentaa JTree-tyyppinen olio. Huomaa taaskin, että puuolioon ei automaattisesti liity liu uttimia. Kuva 8.22: PuuEditori suorituksessa. Kuvassa 8.22 on havainnollistus hieman monipuolisemmasta ohjelmasta PuuEditori (esimerkki 8.17). Esimerkin ohjelmalla on alunperin tietynlainen puurakenne 1, joka esittää maantieteellisten alueiden hierarkiaa. Ohjelmalla on mahdollista lisätä uusia solmuja eri tasoille samoin solmujen tuhoaminen on mahdollistettu. Kuvassa 8.22 oleva tilanne ei ole alkuperäisen tilanteen mukainen, vaan solmuja on poistettu, nimiä on muutettu ja uusia solmuja on lisätty. Ohjelmassa PuuEditori toiminta ei perustu malliluokan määrittelemiseen uudelleen sen enempää kuin puuolion tuottamiin tapahtumiinkaan. Kuvassa 8.17 näkyvään kolmeen nappulaan ohjelman main -metodi kiinnittää tapahtumankäsittelijät, joiden avulla puurakennetta muutetaan. Tapahtumankäsittelijöissä toiminta perustuu olennaisesti siihen, että mallioliosta voidaan selvittää, mikä on viimeisin valittu puun solmu toiminta rakennetaan sen suhteen. 1 Olisi luonnollisesti ollut paljon parempaa, jos puurakenne olisi luettu aluksi jostakin tiedostosta.

Olio-ohjelmointi Käyttöliittymä

Olio-ohjelmointi Käyttöliittymä Olio-ohjelmointi 2016 Käyttöliittymä n Javalla on helppo toteuttaa yksinkertainen graafinen käyttöliittymä n AWT/Swing n JFC (Java Foundation Collection) n AWT-sisältää Javan grafiikka GUI-komponmentit

Lisätiedot

Java ja grafiikka. Ville Sundberg 12.12.2007

Java ja grafiikka. Ville Sundberg 12.12.2007 Java ja grafiikka Ville Sundberg 12.12.2007 What happen Viritetty JPanel Graphics ja Graphics2D ImageIO ja BufferedImage Animaatio ja ajastus Optimoinnista Kehykset import javax.swing.jframe; public class

Lisätiedot

JAVA-OHJELMOINTI 3 op A274615

JAVA-OHJELMOINTI 3 op A274615 JAVA-OHJELMOINTI 3 op A274615 JFC & Swing, Look & Feel, Events Teemu Saarelainen teemu.saarelainen@kyamk.fi Lähteet: http://java.sun.com/docs/books/tutorial/index.html Vesterholm, Kyppö: Java-ohjelmointi,

Lisätiedot

GRAAFISEN KÄYTTÖLIITTYMÄN OHJELMOINTI JAVA SWING

GRAAFISEN KÄYTTÖLIITTYMÄN OHJELMOINTI JAVA SWING GRAAFISEN KÄYTTÖLIITTYMÄN OHJELMOINTI JAVA SWING Käyttöliittymäkomponentteja Sovelluksen pääikkunan luominen Rinnakkainen toiminnallisuus Miten käyttöliittymä lopetetaan? Ikkunan peruskomponentit Mihin

Lisätiedot

Harjoitustyö (TKO_2023)

Harjoitustyö (TKO_2023) Harjoitustyö (TKO_2023) Jyri Lehtonen (72039) (alkuperäinen 29.1.2008) päivitetty: 8.2.2008 1 Tehtävän kuvaus ja analysointi 1.1 Tehtävänanto Tee Java-appletti, joka kysyy käyttäjältä asioita Java-kielestä.

Lisätiedot

Osio 4: Graafinen käyttöliittymä

Osio 4: Graafinen käyttöliittymä Javan Swing-tekniikan perusteet: Muistutus: Tarvitset seuraavia komponentteja harjoituksissa: otsikkoteksti (label) muokkausruutu (text field) komentopainike (button) yhdistelmäruutu (combo box) paneeli

Lisätiedot

Tapahtumapohjainen ohjelmointi. Juha Järvensivu juha.jarvensivu@tut.fi 2007

Tapahtumapohjainen ohjelmointi. Juha Järvensivu juha.jarvensivu@tut.fi 2007 Tapahtumapohjainen ohjelmointi Juha Järvensivu juha.jarvensivu@tut.fi 2007 Sisältö Tapahtumapohjainen ohjelmointi Käyttöliittymän rakenne Pääikkuna (top-level window) Lapsi-ikkuna (child window) Dialogit

Lisätiedot

14. Poikkeukset 14.1

14. Poikkeukset 14.1 14. Poikkeukset 14.1 Sisällys Johdanto. Tarkistettavat ja tarkistamattomat poikkeukset. Poikkeusten tunnistaminen ja sieppaaminen try-catchlauseella. Mitä tehdä siepatulla poikkeuksella? Poikkeusten heittäminen.

Lisätiedot

Sisältö. Johdanto. Tiedostojen lukeminen. Tiedostojen kirjoittaminen. 6.2

Sisältö. Johdanto. Tiedostojen lukeminen. Tiedostojen kirjoittaminen. 6.2 6. Tiedostot 6.1 Sisältö Johdanto. Tiedostojen lukeminen. Tiedostojen kirjoittaminen. 6.2 Johdanto Tiedostoja on käsitelty uudelleenohjattujen standardisyöteja tulostusvirtojen avulla. Tiedostoja voidaan

Lisätiedot

Sisältö Johdanto. Tiedostojen lukeminen. Tiedostojen kirjoittaminen. 26.2

Sisältö Johdanto. Tiedostojen lukeminen. Tiedostojen kirjoittaminen. 26.2 26. Tiedostot 26.1 Sisältö Johdanto. Tiedostojen lukeminen. Tiedostojen kirjoittaminen. 26.2 Johdanto Tiedostoja on käsitelty uudelleenohjattujen standardisyöte- ja tulostusvirtojen avulla. Tiedostoja

Lisätiedot

JAVA on ohjelmointikieli, mikä on kieliopiltaan hyvin samankaltainen, jopa identtinen mm. C++

JAVA on ohjelmointikieli, mikä on kieliopiltaan hyvin samankaltainen, jopa identtinen mm. C++ JAVA alkeet JAVA on ohjelmointikieli, mikä on kieliopiltaan hyvin samankaltainen, jopa identtinen mm. C++ ja Javascriptin kanssa. Huom! JAVA ja JavaScript eivät silti ole sama asia, eivätkä edes sukulaiskieliä.

Lisätiedot

Sisältö. Johdanto. Tiedostojen lukeminen. Tiedostojen kirjoittaminen. 6.2

Sisältö. Johdanto. Tiedostojen lukeminen. Tiedostojen kirjoittaminen. 6.2 6. Tiedostot 6.1 Sisältö Johdanto. Tiedostojen lukeminen. Tiedostojen kirjoittaminen. 6.2 Johdanto Tiedostoja on käsitelty uudelleenohjattujen standardisyöte- ja tulostusvirtojen avulla. Tiedostoja voidaan

Lisätiedot

Listarakenne (ArrayList-luokka)

Listarakenne (ArrayList-luokka) Listarakenne (ArrayList-luokka) Mikä on lista? Listan määrittely ArrayList-luokan metodeita Listan läpikäynti Listan läpikäynti indeksin avulla Listan läpikäynti iteraattorin avulla Listaan lisääminen

Lisätiedot

Sisällys. 14. Poikkeukset. Johdanto. Johdanto

Sisällys. 14. Poikkeukset. Johdanto. Johdanto Sisällys 14. Poikkeukset Johdanto. Tarkistettavat ja tarkistamattomat poikkeukset. Poikkeusten tunnistaminen ja sieppaaminen try-catchlauseella. Mitä tehdä siepatulla poikkeuksella? Poikkeusten heittäminen.

Lisätiedot

812341A Olio-ohjelmointi Peruskäsitteet jatkoa

812341A Olio-ohjelmointi Peruskäsitteet jatkoa 812341A Olio-ohjelmointi 2106 Peruskäsitteet jatkoa Luokkakohtaiset piirteet n Yhteisiä kaikille saman luokan olioille n Liittyvät luokkaan, eivät yksittäiseen olioon n Kaikki ko. luokan oliot voivat käyttää

Lisätiedot

Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä

Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä Matti Luukkainen 10.12.2009 Tässä esitetty esimerkki on mukaelma ja lyhennelmä Robert Martinin kirjasta Agile and Iterative Development löytyvästä

Lisätiedot

Sisällys. 14. Poikkeukset. Johdanto. Johdanto

Sisällys. 14. Poikkeukset. Johdanto. Johdanto Sisällys 14. Poikkeukset Johdanto. Tarkistettavat ja tarkistamattomat poikkeukset. Miten varautua poikkeukseen metodissa? Poikkeusten tunnistaminen ja sieppaaminen try-catchlauseella. Mitä tehdä siepatulla

Lisätiedot

14. Poikkeukset 14.1

14. Poikkeukset 14.1 14. Poikkeukset 14.1 Sisällys Johdanto. Tarkistettavat ja tarkistamattomat poikkeukset. Miten varautua poikkeukseen metodissa? Poikkeusten tunnistaminen ja sieppaaminen try-catchlauseella. Mitä tehdä siepatulla

Lisätiedot

Vertailulauseet. Ehtolausekkeet. Vertailulauseet. Vertailulauseet. if-lauseke. if-lauseke. Javan perusteet 2004

Vertailulauseet. Ehtolausekkeet. Vertailulauseet. Vertailulauseet. if-lauseke. if-lauseke. Javan perusteet 2004 Vertailulauseet Ehtolausekkeet Ehdot, valintalausekkeet Boolean-algebra == yhtäsuuruus!= erisuuruus < pienempi suurempi >= suurempi tai yhtäsuuri Esimerkkejä: int i=7; int j=10;

Lisätiedot

Rinnakkaisohjelmointi kurssi. Opintopiiri työskentelyn raportti

Rinnakkaisohjelmointi kurssi. Opintopiiri työskentelyn raportti Rinnakkaisohjelmointi kurssi Opintopiiri työskentelyn raportti Opintopiiri: Heikki Karimo, Jesse Paakkari ja Keijo Karhu Päiväys: 15.12.2006 Ohjelmointitehtävä C i C i : Säikeet ja kriittisen vaiheen kontrollointi

Lisätiedot

Tietojen syöttäminen ohjelmalle. Tietojen syöttäminen ohjelmalle Scanner-luokan avulla

Tietojen syöttäminen ohjelmalle. Tietojen syöttäminen ohjelmalle Scanner-luokan avulla Tietojen syöttäminen ohjelmalle Tähän mennessä on käsitelty Javan tulostuslauseet System.out.print ja System.out.println sekä ohjelman perusrakenneosat (muuttujat, vakiot, lauseet). Jotta päästään tekemään

Lisätiedot

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A274615 JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++?

JAVA-PERUSTEET. JAVA-OHJELMOINTI 3op A274615 JAVAN PERUSTEET LYHYT KERTAUS JAVAN OMINAISUUKSISTA JAVAN OMINAISUUKSIA. Java vs. C++? JAVA-OHJELMOINTI 3op A274615 JAVAN PERUSTEET LYHYT KERTAUS Teemu Saarelainen teemu.saarelainen@kyamk.fi Lähteet: http://java.sun.com/docs/books/tutorial/index.html Vesterholm, Kyppö: Java-ohjelmointi,

Lisätiedot

Java-API, rajapinnat, poikkeukset, UML,...

Java-API, rajapinnat, poikkeukset, UML,... Java-API, rajapinnat, r poikkeukset, UML,... Janne Käki 12.10.2006 Keskeisimmät Java-API:n pakkaukset API = Application Programming Interface eli sovellusohjelmointirajapinta (!) pakkaus (engl. package)

Lisätiedot

Ohjelmoinnin jatkokurssi, kurssikoe 28.4.2014

Ohjelmoinnin jatkokurssi, kurssikoe 28.4.2014 Ohjelmoinnin jatkokurssi, kurssikoe 28.4.2014 Kirjoita jokaiseen palauttamaasi konseptiin kurssin nimi, kokeen päivämäärä, oma nimi ja opiskelijanumero. Vastaa kaikkiin tehtäviin omille konsepteilleen.

Lisätiedot

Mikä yhteyssuhde on?

Mikä yhteyssuhde on? 1 Yhteyssuhde Mikä yhteyssuhde on? Yhteyssuhde Javalla Konstruktorit set-ja get-metodit tostring-metodi Pääohjelma 1 Mikä yhteyssuhde on? Tili - : String - : double * 1 Asiakas - hetu: String - : String

Lisätiedot

Olio-ohjelmointi Javalla

Olio-ohjelmointi Javalla 1 Olio-ohjelmointi Javalla Olio-ohjelmointi Luokka Attribuutit Konstruktori Olion luominen Metodit Olion kopiointi Staattinen attribuutti ja metodi Yksinkertainen ohjelmaluokka Ohjelmaluokka 1 Olio-ohjelmointi

Lisätiedot

Luento 6. T Ohjelmoinnin jatkokurssi T1 & T Ohjelmoinnin jatkokurssi L1. Luennoitsija: Otto Seppälä

Luento 6. T Ohjelmoinnin jatkokurssi T1 & T Ohjelmoinnin jatkokurssi L1. Luennoitsija: Otto Seppälä Luento 6 T-106.1240 Ohjelmoinnin jatkokurssi T1 & T-106.1243 Ohjelmoinnin jatkokurssi L1 Luennoitsija: Otto Seppälä Kurssin WWW: http://www.cs.hut.fi/opinnot/t-106.1240/s2007 Oma Grafiikka Swing-käyttöliittymässä

Lisätiedot

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Taulukot & Periytyminen Taulukot: Array Taulukko Javassa pitää aina perustaa (new) Yksinkertaisessa tilanteessa taulukon koko tiedetään etukäteen ja

Lisätiedot

Ohjelmointi 2 / 2008 Välikoe / Pöytätestaa seuraava ohjelma.

Ohjelmointi 2 / 2008 Välikoe / Pöytätestaa seuraava ohjelma. Välikoe / 20.3 Vastaa neljään (4) tehtävään. Jos vastaat 5:een, 4 huonointa arvostellaan. Kunkin tehtävän vastaus eri konseptille. 1. Pöytätesti Pöytätestaa seuraava ohjelma. Tutki ohjelman toimintaa pöytätestillä

Lisätiedot

9. Periytyminen Javassa 9.1

9. Periytyminen Javassa 9.1 9. Periytyminen Javassa 9.1 Sisällys Periytymismekanismi Java-kielessä. Piirteiden näkyvyys periytymisessä. Ilmentymämetodien korvaaminen. Luokkametodien peittäminen. Super-attribuutti. Override-annotaatio.

Lisätiedot

Harjoitus 7. 1. Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti:

Harjoitus 7. 1. Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti: Harjoitus 7 1. Olkoon olemassa luokat Lintu ja Pelikaani seuraavasti: class Lintu //Kentät private int _siivenpituus; protected double _aivojenkoko; private bool _osaakolentaa; //Ominaisuudet public int

Lisätiedot

Harjoitus 3: Flash-komponenttiarkkitehtuuri (18.3.2016)

Harjoitus 3: Flash-komponenttiarkkitehtuuri (18.3.2016) Harjoitus 3: Flash-komponenttiarkkitehtuuri (18.3.2016) Tietokoneavusteinen opetus -kurssilla opetetaan Adobe Flash CS6:n käyttämistä neljänä kertana: 11.3.2016, 15.3.2016, 18.3.2016 ja 1.4.2016. Harjoituskerroilla

Lisätiedot

Rajapinta (interface)

Rajapinta (interface) 1 Rajapinta (interface) Mikä rajapinta on? Rajapinta ja siitä toteutettu luokka Monimuotoisuus ja dynaaminen sidonta Rajapinta vs periytyminen 1 Mikä rajapinta on? Rajapintoja käytetään, kun halutaan määritellä

Lisätiedot

Poikkeustenkäsittely

Poikkeustenkäsittely 1 Poikkeustenkäsittely Mitä poikkeustenkäsittely tarkoittaa? Poikkeuksen käsitteleminen Poikkeusluokkien hierarkia Poikkeuksen heittäminen 1 Mitä poikkeustenkäsittely tarkoittaa? Poikkeus (Exception) on

Lisätiedot

Osio 4: Graafinen käyttöliittymä

Osio 4: Graafinen käyttöliittymä Tavoite: Opiskelija tuntee käyttöliittymän suunnittelun perusteita Opiskelija tuntee Javan Swing-tekniikan perusteet Opiskelija osaa työasemakäyttöliittymän toteutuksen perusteet käyttäen Javan Swing-tekniikkaa

Lisätiedot

JavaRMI 1 JAVA RMI. Rinnakkaisohjelmoinnin projekti 1 osa C Tekijät: Taru Itäpelto-Hu Jaakko Nissi Mikko Ikävalko

JavaRMI 1 JAVA RMI. Rinnakkaisohjelmoinnin projekti 1 osa C Tekijät: Taru Itäpelto-Hu Jaakko Nissi Mikko Ikävalko JavaRMI 1 JAVA RMI Rinnakkaisohjelmoinnin projekti 1 osa C Tekijät: Taru Itäpelto-Hu Jaakko Nissi Mikko Ikävalko JavaRMI 2 Table of Contents...1 JAVA RMI...1 Yleistä...4 Arkkitehtuuri...5 Java RMI kerrosarkkitehtuuri...5

Lisätiedot

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python Ohjelmoinnin perusteet Y Python T-106.1208 12.4.2010 T-106.1208 Ohjelmoinnin perusteet Y 12.4.2010 1 / 34 Graafiset käyttöliittymät Tähän asti kirjoitetuissa ohjelmissa on ollut tekstipohjainen käyttöliittymä.

Lisätiedot

Ohjelmointi 2 / 2010 Välikoe / 26.3

Ohjelmointi 2 / 2010 Välikoe / 26.3 Ohjelmointi 2 / 2010 Välikoe / 26.3 Välikoe / 26.3 Vastaa neljään (4) tehtävään ja halutessa bonustehtäviin B1 ja/tai B2, (tuovat lisäpisteitä). Bonustehtävät saa tehdä vaikkei olisi tehnyt siihen tehtävään

Lisätiedot

Java kahdessa tunnissa. Jyry Suvilehto

Java kahdessa tunnissa. Jyry Suvilehto Java kahdessa tunnissa Jyry Suvilehto Ohjelma Ohjelmointiasioita alkeista nippelitietoon n. 45 min Tauko 10 min Oliot, luokat ja muut kummajaiset n. 45 min Kysykää Sisältöä ei oikeasti ole 2x45 min täytteeksi,

Lisätiedot

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti:

Ohjelmassa henkilön etunimi ja sukunimi luetaan kahteen muuttujaan seuraavasti: 1 (7) Tiedon lukeminen näppäimistöltä Scanner-luokan avulla Miten ohjelma saa käyttöönsä käyttäjän kirjoittamaa tekstiä? Järjestelmässä on olemassa ns. syöttöpuskuri näppäimistöä varten. Syöttöpuskuri

Lisätiedot

1 Tehtävän kuvaus ja analysointi

1 Tehtävän kuvaus ja analysointi Olio-ohjelmoinnin harjoitustyön dokumentti Jyri Lehtonen (72039) Taneli Tuovinen (67160) 1 Tehtävän kuvaus ja analysointi 1.1 Tehtävänanto Tee luokka, jolla mallinnetaan sarjaan kytkettyjä kondensaattoreita.

Lisätiedot

15. Ohjelmoinnin tekniikkaa 15.1

15. Ohjelmoinnin tekniikkaa 15.1 15. Ohjelmoinnin tekniikkaa 15.1 Sisällys For-each-rakenne. Lueteltu tyyppi enum. Override-annotaatio. Geneerinen ohjelmointi. 15.2 For-each-rakenne For-rakenteen variaatio taulukoiden ja muiden kokoelmien

Lisätiedot

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python Ohjelmoinnin perusteet Y Python T-106.1208 15.3.2010 T-106.1208 Ohjelmoinnin perusteet Y 15.3.2010 1 / 56 Tiedostoista: tietojen tallentaminen ohjelman suorituskertojen välillä Monissa sovelluksissa ohjelman

Lisätiedot

A) on käytännöllinen ohjelmointitekniikka. = laajennetaan aikaisemmin tehtyjä luokkia (uudelleenkäytettävyys)

A) on käytännöllinen ohjelmointitekniikka. = laajennetaan aikaisemmin tehtyjä luokkia (uudelleenkäytettävyys) 1(37) PERIYTYMINEN (inheritance) YLILUOKKA (superclass) ALILUOKKA (subclass) A) on käytännöllinen ohjelmointitekniikka = laajennetaan aikaisemmin tehtyjä luokkia (uudelleenkäytettävyys) B) on käsitteiden

Lisätiedot

15. Ohjelmoinnin tekniikkaa 15.1

15. Ohjelmoinnin tekniikkaa 15.1 15. Ohjelmoinnin tekniikkaa 15.1 Sisällys For-each-rakenne. Geneerinen ohjelmointi. Lueteltu tyyppi enum. 15.2 For-each-rakenne For-rakenteen variaatio taulukoiden ja muiden kokoelmien silmukoimiseen:

Lisätiedot

9. Periytyminen Javassa 9.1

9. Periytyminen Javassa 9.1 9. Periytyminen Javassa 9.1 Sisällys Periytymismekanismi Java-kielessä. Piirteiden näkyvyys periytymisessä. Metodien korvaaminen ja super-attribuutti. Attribuutin peittäminen periytymisen kautta. Rakentajat

Lisätiedot

Tapahtumapohjainen ohjelmointi

Tapahtumapohjainen ohjelmointi Tapahtumapohjainen ohjelmointi Juha-Matti Vanhatupa (vanhan kurssin Graafisen käyttöliittymän ohjelmointi materiaalia) Erot perinteisiin sovelluksiin Sovelluksen kulku ei ole ennalta tiedossa. Start A

Lisätiedot

Java UI-komponentit (JTable) Juha Järvensivu juha.jarvensivu@tut.fi 2007

Java UI-komponentit (JTable) Juha Järvensivu juha.jarvensivu@tut.fi 2007 Java UI-komponentit (JTable) Juha Järvensivu juha.jarvensivu@tut.fi 2007 JTable Datan esittäminen taulukkomuodossa Datan valitseminen taulukosta Datan muokkaaminen (lisääminen, muokkaaminen, poistaminen)

Lisätiedot

P e d a c o d e ohjelmointikoulutus verkossa

P e d a c o d e ohjelmointikoulutus verkossa P e d a c o d e ohjelmointikoulutus verkossa Java-kielen perusteet Teoria ja ohjelmointitehtävät Java-kielen perusteet 3 YLEISKATSAUS KURSSIN SISÄLTÖIHIN 10 JAVA-KIELEN PERUSTEET 10 OPISKELUN ALOITTAMINEN

Lisätiedot

Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo

Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo Concurrency - Rinnakkaisuus Group: 9 Joni Laine Juho Vähätalo Sisällysluettelo 1. Johdanto... 3 2. C++ thread... 4 3. Python multiprocessing... 6 4. Java ExecutorService... 8 5. Yhteenveto... 9 6. Lähteet...

Lisätiedot

Sovelmat. Janne Käki

Sovelmat. Janne Käki Sovelmat Janne Käki 24.11.2006 Sovellus extends JFrame public static void main(string[] args), joka tyypillisesti vain luo kehysluokan ilmentymän luontimetodi Sovelma extends JApplet ei main-metodia, ei

Lisätiedot

Sisällys. JAVA-OHJELMOINTI Osa 7: Abstrakti luokka ja rajapinta. Abstraktin luokan idea. Abstrakti luokka ja metodi. Esimerkki

Sisällys. JAVA-OHJELMOINTI Osa 7: Abstrakti luokka ja rajapinta. Abstraktin luokan idea. Abstrakti luokka ja metodi. Esimerkki Sisällys JAVA-OHJELMOINTI Osa 7: Abstrakti luokka ja rajapinta Abstrakti luokka ja metodi Rajapintamäärittely (interface) Eero Hyvönen Tietojenkäsittelytieteen laitos Helsingin yliopisto 13.10.2000 E.

Lisätiedot

Javan perusteita. Janne Käki

Javan perusteita. Janne Käki Javan perusteita Janne Käki 20.9.2006 Muutama perusasia Tietokone tekee juuri (ja vain) sen, mitä käsketään. Tietokone ymmärtää vain syntaksia (sanojen kirjoitusasua), ei semantiikkaa (sanojen merkitystä).

Lisätiedot

Tehtävä 1. Tehtävä 2. Arvosteluperusteet Koherentti selitys Koherentti esimerkki

Tehtävä 1. Tehtävä 2. Arvosteluperusteet Koherentti selitys Koherentti esimerkki Tehtävä 1 Koherentti selitys Koherentti esimerkki ½p ½p Tehtävä 2 Täysiin pisteisiin edellytetään pelaajien tulostamista esimerkin järjestyksessä. Jos ohjelmasi tulostaa pelaajat jossain muussa järjestyksessä,

Lisätiedot

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Rajapinnat ja sisäluokat

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Rajapinnat ja sisäluokat Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Rajapinnat ja sisäluokat Rajapinnat Java-kieli ei tue luokkien moniperintää. Jokaisella luokalla voi olla vain yksi välitön yliluokka. Toisinaan olisi

Lisätiedot

Luokka Murtoluku uudelleen. Kirjoitetaan luokka Murtoluku uudelleen niin, että murtolukujen sieventäminen on mahdollista.

Luokka Murtoluku uudelleen. Kirjoitetaan luokka Murtoluku uudelleen niin, että murtolukujen sieventäminen on mahdollista. 1 Luokka Murtoluku uudelleen Kirjoitetaan luokka Murtoluku uudelleen niin, että murtolukujen sieventäminen on mahdollista. Sievennettäessä tarvitaan osoittajan ja nimittäjän suurin yhteinen tekijä (syt).

Lisätiedot

Graafisen käyttöliittymän ohjelmointi

Graafisen käyttöliittymän ohjelmointi TIE-11300 Tietotekniikan vaihtuva-alainen kurssi Graafisen käyttöliittymän ohjelmointi Luento 2 Tapahtumapohjainen ohjelmointi Juha-Matti Vanhatupa Sisältö Tapahtumapohjainen ohjelmointi Tapahtumakuuntelijoiden

Lisätiedot

Olio-ohjelmointi Suunnittelumallit Adapter ja Composite. 1. Adapter

Olio-ohjelmointi Suunnittelumallit Adapter ja Composite. 1. Adapter Olio-ohjelmointi Suunnittelumallit Adapter ja Composite Rakennemalleissa päähuomio kohdistetaan siihen, miten luokkia ja olioita yhdistellään muodostamaan laajempia rakenteita. Rakenteelliset luokkamallit

Lisätiedot

Graafinen käyttöliittymä, osa 2

Graafinen käyttöliittymä, osa 2 Graafinen käyttöliittymä, osa 2 Tapahtumankäsittely, käyttöliittymäkomponentteja MVC-malli M Malli V Näkymä Päivitys C Ohjain Päivitys Tapahtumat MVC-malli jakaa sovelluksen malli- ja näkymäkerroksiin.

Lisätiedot

Pakkauksen kokoaminen

Pakkauksen kokoaminen 13. Pakkaukset 13.1 Sisällys Pakkauksen kokoaminen (package). Pakkaukset ja hakemistorakenne. Pakkauksen luokkien käyttö muissa pakkauksissa (import). Pakkaukset ja näkyvyys. 13.2 Pakkauksen kokoaminen

Lisätiedot

Osio 4: Graafinen käyttöliittymä

Osio 4: Graafinen käyttöliittymä Javan Swing-tekniikan perusteet: Muistutus: Tarvitset seuraavia komponentteja harjoituksissa: otsikkoteksti (label) muokkausruutu (text field) komentopainike (button) yhdistelmäruutu (combo box) paneeli

Lisätiedot

LUKU 17 MUUTAMIA JAVA FX -SOVELLUKSIA. Tässä dokumentissa esitellään muutamia Java FX -sovelluksia.

LUKU 17 MUUTAMIA JAVA FX -SOVELLUKSIA. Tässä dokumentissa esitellään muutamia Java FX -sovelluksia. LUKU 17 MUUTAMIA JAVA FX -SOVELLUKSIA Tässä dokumentissa esitellään muutamia Java FX -sovelluksia. Kaikista sovelluksista esitellään niiden ohjelmakoodi muutamine selityksineen ja lisäksi on kuva kunkin

Lisätiedot

16. Javan omat luokat 16.1

16. Javan omat luokat 16.1 16. Javan omat luokat 16.1 Sisällys Johdanto. Object-luokka: tostring-, equals-, clone- ja getclass-metodit. Comparable-rajapinta: compareto-metodi. Vector- ja ArrayList-luokat. 16.2 Javan omat luokat

Lisätiedot

Metodien tekeminen Javalla

Metodien tekeminen Javalla 1 Metodien tekeminen Javalla Mikä metodi on? Metodin syntaksi Metodi ja sen kutsuminen Parametreista Merkkijonot ja metodi Taulukot ja metodi 1 Mikä metodi on? Metodilla toteutetaan luokkaan toiminnallisuutta.

Lisätiedot

Harjoitustyö: virtuaalikone

Harjoitustyö: virtuaalikone Harjoitustyö: virtuaalikone Toteuta alla kuvattu virtuaalikone yksinkertaiselle olio-orientoituneelle skriptauskielelle. Paketissa on testaamista varten mukana kaksi lyhyttä ohjelmaa. Ohjeita Noudata ohjelman

Lisätiedot

Harjoitus 2: Oppijan aktivointi (15.3.2016)

Harjoitus 2: Oppijan aktivointi (15.3.2016) Harjoitus 2: Oppijan aktivointi (15.3.2016) Tietokoneavusteinen opetus -kurssilla opetetaan Adobe Flash CS6:n käyttämistä neljänä kertana: 11.3.2016, 15.3.2016, 18.3.2016 ja 1.4.2016. Harjoituskerroilla

Lisätiedot

Sisältö. 22. Taulukot. Yleistä. Yleistä

Sisältö. 22. Taulukot. Yleistä. Yleistä Sisältö 22. Taulukot Yleistä. Esittely ja luominen. Alkioiden käsittely. Kaksiulotteinen taulukko. Taulukko metodin parametrina. Taulukko ja HelloWorld-ohjelma. Taulukko paluuarvona. 22.1 22.2 Yleistä

Lisätiedot

T740103 Olio-ohjelmointi Osa 5: Periytyminen ja polymorfismi Jukka Jauhiainen OAMK Tekniikan yksikkö 2010

T740103 Olio-ohjelmointi Osa 5: Periytyminen ja polymorfismi Jukka Jauhiainen OAMK Tekniikan yksikkö 2010 12. Periytyminen Johdantoa Käytännössä vähänkään laajemmissa ohjelmissa joudutaan laatimaan useita luokkia, joiden pitäisi pystyä välittämään tietoa toisilleen. Ohjelmien ylläpidon kannalta olisi lisäksi

Lisätiedot

Rinnakkaisohjelmointi, Syksy 2006

Rinnakkaisohjelmointi, Syksy 2006 Rinnakkaisohjelmointi, Syksy 2006 17.12.2006 Opintopiiri WTF Mika Holmström Paula Kemppi Janne Piippo Lasse Lukkari Javan semaforit 1. Menetelmän käyttötarkoitus ja sovellusalue Semaforin idea kehitettiin

Lisätiedot

HSMT TCP- ja UDP-soketeista

HSMT TCP- ja UDP-soketeista HSMT TCP- ja UDP-soketeista Ville Leppänen HSMT, c Ville Leppänen, IT, Turun yliopisto, 2011 p.1/41 Missä mennään... 1. Johdanto (1h) 2. Säikeet (2h) 3. Samanaikaisuudesta (2h) 4. Hajautetuista sovelluksista

Lisätiedot

Sisällys. 9. Periytyminen Javassa. Periytymismekanismi Java-kielessä. Periytymismekanismi Java-kielessä

Sisällys. 9. Periytyminen Javassa. Periytymismekanismi Java-kielessä. Periytymismekanismi Java-kielessä Sisällys 9. Periytyminen Javassa Periytymismekanismi Java-kielessä. Piirteiden näkyvyys periytymisessä. Metodien korvaaminen ja super-attribuutti. Attribuutin peittäminen periytymisen kautta. Rakentajat

Lisätiedot

Sisällys. 11. Javan toistorakenteet. Laskurimuuttujat. Yleistä

Sisällys. 11. Javan toistorakenteet. Laskurimuuttujat. Yleistä Sisällys 11. Javan toistorakenteet Laskuri- ja lippumuuttujat.. Tyypillisiä ohjelmointivirheitä: Silmukan rajat asetettu kierroksen verran väärin. Ikuinen silmukka. Silmukoinnin lopettaminen break-lauseella.

Lisätiedot

Sisällys. Metodien kuormittaminen. Luokkametodit ja -attribuutit. Rakentajat. Metodien ja muun luokan sisällön järjestäminen. 6.2

Sisällys. Metodien kuormittaminen. Luokkametodit ja -attribuutit. Rakentajat. Metodien ja muun luokan sisällön järjestäminen. 6.2 6. Metodit 6.1 Sisällys Metodien kuormittaminen. Luokkametodit ja -attribuutit. Rakentajat. Metodien ja muun luokan sisällön järjestäminen. 6.2 Oliot viestivät metodeja kutsuen Olio-ohjelmoinnissa ohjelma

Lisätiedot

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op. Poikkeukset ja tietovirrat: Virhetilanteiden ja syötevirtojen käsittely

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op. Poikkeukset ja tietovirrat: Virhetilanteiden ja syötevirtojen käsittely Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Poikkeukset ja tietovirrat: Virhetilanteiden ja syötevirtojen käsittely Poikkeukset Poikkeuksella tarkoitetaan yllättävää ajonaikaista tilannetta, joka

Lisätiedot

Periytyminen (inheritance)

Periytyminen (inheritance) 1 Periytyminen (inheritance) Mitä periytyminen on? Yli- ja aliluokka Konstruktorit Get- ja set-metodi Muut metodit tostring Yksinkertainen pääohjelma 1 Mitä periytyminen on? Periytymisen avulla olemassa

Lisätiedot

Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa();

Olion elinikä. Olion luominen. Olion tuhoutuminen. Olion tuhoutuminen. Kissa rontti = null; rontti = new Kissa(); Sisällys 7. Oliot ja viitteet Olio Java-kielessä. Olion luominen, elinikä ja tuhoutuminen. Viitteiden käsittelyä: sijoitus, vertailu ja varautuminen null-arvoon. Viite metodin paluuarvona.. 7.1 7.2 Olio

Lisätiedot

Sisällys. 9. Periytyminen Javassa. Periytymismekanismi Java-kielessä. Periytymismekanismi Java-kielessä

Sisällys. 9. Periytyminen Javassa. Periytymismekanismi Java-kielessä. Periytymismekanismi Java-kielessä Sisällys 9. Periytyminen Javassa Periytymismekanismi Java-kielessä. Piirteiden näkyvyys periytymisessä. Metodien korvaaminen ja super-attribuutti. Attribuutin peittäminen periytymisen kautta. Rakentajat

Lisätiedot

Yleistä. Nyt käsitellään vain taulukko (array), joka on saman tyyppisten muuttujien eli alkioiden (element) kokoelma.

Yleistä. Nyt käsitellään vain taulukko (array), joka on saman tyyppisten muuttujien eli alkioiden (element) kokoelma. 2. Taulukot 2.1 Sisältö Yleistä. Esittely ja luominen. Alkioiden käsittely. Kaksiulotteinen taulukko. Taulukko operaation parametrina. Taulukko ja HelloWorld-ohjelma. Taulukko paluuarvona. 2.2 Yleistä

Lisätiedot

<applet code="simpleaudioapplet.class" width=300 height=300>

<applet code=simpleaudioapplet.class width=300 height=300> SimpleAudioApplet.java /* */ // Appletti joka soittaa käynnistyttyään oletushakemistossa // olevan sound.wav-nimisen äänitiedoston. public class

Lisätiedot

Sisällys. 12. Näppäimistöltä lukeminen. Yleistä. Yleistä 12.1 12.2 12.3 12.4

Sisällys. 12. Näppäimistöltä lukeminen. Yleistä. Yleistä 12.1 12.2 12.3 12.4 Sisällys 12. Näppäimistöltä lukeminen Arvojen lukeminen näppäimistöltä yleisesti. Arvojen lukeminen näppäimistöltä Java-kielessä.. Luetun arvon tarkistaminen. Tietovirrat ja ohjausmerkit. Scanner-luokka.

Lisätiedot

Sisältö. 2. Taulukot. Yleistä. Yleistä

Sisältö. 2. Taulukot. Yleistä. Yleistä Sisältö 2. Taulukot Yleistä. Esittely ja luominen. Alkioiden käsittely. Kaksiulotteinen taulukko. Taulukko operaation parametrina. Taulukko ja HelloWorld-ohjelma. Taulukko paluuarvona. 2.1 2.2 Yleistä

Lisätiedot

Informaatioteknologian laitos Olio-ohjelmoinnin perusteet / Salo 15.2.2006

Informaatioteknologian laitos Olio-ohjelmoinnin perusteet / Salo 15.2.2006 TURUN YLIOPISTO DEMO III Informaatioteknologian laitos tehtävät Olio-ohjelmoinnin perusteet / Salo 15.2.2006 1. Tässä tehtävässä tarkastellaan erääntyviä laskuja. Lasku muodostaa oman luokkansa. Laskussa

Lisätiedot

Graafinen käyttöliittymä, osa 3

Graafinen käyttöliittymä, osa 3 Graafinen käyttöliittymä, osa 3 Hiiritapahtumat, valikot, Look and Feel Taustaa: painikkeen käsittely Painikkeen tapahtumalähde on JButton-komponentti. Tapahtuma synnyttää aina tapahtumaolion. painikkeelle

Lisätiedot

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Pakkaukset ja määreet

Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Pakkaukset ja määreet Opintojakso TT00AA11 Ohjelmoinnin jatko (Java): 3 op Pakkaukset ja määreet Pakkaukset ja määreet Toisiinsa liittyvät luokkatiedostot voidaan koota pakkauksiksi. Luo hierarkiaa ja järjestystä ohjelmistotuotteeseen.

Lisätiedot

11. Javan toistorakenteet 11.1

11. Javan toistorakenteet 11.1 11. Javan toistorakenteet 11.1 Sisällys Laskuri- ja lippumuuttujat. Sisäkkäiset silmukat. Tyypillisiä ohjelmointivirheitä: Silmukan rajat asetettu kierroksen verran väärin. Ikuinen silmukka. Silmukoinnin

Lisätiedot

Tapahtumapohjainen ohjelmointi. Juha Järvensivu 2008

Tapahtumapohjainen ohjelmointi. Juha Järvensivu 2008 Tapahtumapohjainen ohjelmointi Juha Järvensivu juha.jarvensivu@tut.fi 2008 Sisältö Tapahtumapohjainen ohjelmointi Käyttöliittymän rakenne Pääikkuna (top-level window) Lapsi-ikkuna (child window) Dialogit

Lisätiedot

Kompositio. Mikä komposition on? Kompositio vs. yhteyssuhde Kompositio Javalla Konstruktorit set-ja get-metodit tostring-metodi Pääohjelma

Kompositio. Mikä komposition on? Kompositio vs. yhteyssuhde Kompositio Javalla Konstruktorit set-ja get-metodit tostring-metodi Pääohjelma 1 Kompositio Mikä komposition on? Kompositio vs. yhteyssuhde Kompositio Javalla Konstruktorit set-ja get-metodit tostring-metodi Pääohjelma 1 Mikä kompositio on? Tili - : String - : double 1 1 Kayttoraja

Lisätiedot

Luento 5. T Ohjelmoinnin jatkokurssi T1 & T Ohjelmoinnin jatkokurssi L1. Luennoitsija: Otto Seppälä

Luento 5. T Ohjelmoinnin jatkokurssi T1 & T Ohjelmoinnin jatkokurssi L1. Luennoitsija: Otto Seppälä Luento 5 T-106.1240 Ohjelmoinnin jatkokurssi T1 & T-106.1243 Ohjelmoinnin jatkokurssi L1 Luennoitsija: Otto Seppälä Kurssin WWW: http://www.cs.hut.fi/opinnot/t-106.1240/s2007 Patterns & AWT/Swing Observer-pattern

Lisätiedot

ASENNUS- JA KÄYTTÖOHJE

ASENNUS- JA KÄYTTÖOHJE ASENNUS- JA KÄYTTÖOHJE YKSIKKÖHINTA SOPIMUKSEN TOTEUTUNEET MÄÄRÄT-SOVELLUS CMPRO5 VERSIO 2.8 PÄIVITETTY HEINÄKUU 2010 COPYRIGHT 2010 ARTEMIS FINLAND OY. ALL RIGHTS RESERVED. KÄYTTÖOHJE SIVU 2 (12) SISÄLLYSLUETTELO

Lisätiedot

Pakkauksen kokoaminen

Pakkauksen kokoaminen 13. Pakkaukset 13.1 Sisällys Pakkauksen kokoaminen package-määrettä käyttäen. Pakkaukset ja hakemistorakenne. Pakkauksen luokkien käyttö muissa pakkauksissa importlauseen avulla. Pakkaukset ja näkyvyys.

Lisätiedot

11. Javan valintarakenteet 11.1

11. Javan valintarakenteet 11.1 11. Javan valintarakenteet 11.1 Sisällys If- ja if-else-lauseet. Orpo else. Valintaa toisin: switch-lause. 11.2 If-lause Merkitään varatulla sanalla if. Kuvaa yksisuuntaisen päätöksen: rakenteen lauseet

Lisätiedot

12. Javan toistorakenteet 12.1

12. Javan toistorakenteet 12.1 12. Javan toistorakenteet 12.1 Sisällys Yleistä toistorakenteista. Laskurimuuttujat. While-, do-while- ja for-lauseet. Laskuri- ja lippumuuttujat. Tyypillisiä ohjelmointivirheitä. Silmukan rajat asetettu

Lisätiedot

Java layoutit. Juha Järvensivu juha.jarvensivu@tut.fi 2007

Java layoutit. Juha Järvensivu juha.jarvensivu@tut.fi 2007 Java layoutit Juha Järvensivu juha.jarvensivu@tut.fi 2007 Layout Container Container LayoutManager Component Component Component Komponentin koko minimikoko setminumumsize(dimension d) Useimmat layoutmanagerit

Lisätiedot

Ohjelmointitaito (ict1td002, 12 op) Kevät 2008. 1. Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen raine.kauppinen@haaga-helia.

Ohjelmointitaito (ict1td002, 12 op) Kevät 2008. 1. Java-ohjelmoinnin alkeita. Tietokoneohjelma. Raine Kauppinen raine.kauppinen@haaga-helia. Ohjelmointitaito (ict1td002, 12 op) Kevät 2008 Raine Kauppinen raine.kauppinen@haaga-helia.fi 1. Java-ohjelmoinnin alkeita Tietokoneohjelma Java-kieli ja Eclipse-ympäristö Java-ohjelma ja ohjelmaluokka

Lisätiedot

JReleaser Yksikkötestaus ja JUnit. Mikko Mäkelä 6.11.2002

JReleaser Yksikkötestaus ja JUnit. Mikko Mäkelä 6.11.2002 JReleaser Yksikkötestaus ja JUnit Mikko Mäkelä 6.11.2002 Sisältö Johdanto yksikkötestaukseen JUnit yleisesti JUnit Framework API (TestCase, TestSuite) Testien suorittaminen eri työkaluilla Teknisiä käytäntöjä

Lisätiedot

Luokan sisällä on lista

Luokan sisällä on lista 1 Luokan sisällä on lista Luokan sisällä lista Listan sisältävä luokka Konstruktorit get-metodi Lista muissa metodeissa addxx-metodi Yksinkertainen pääohjelma Kertauksen List-luokan metodeja 1 Luokan sisällä

Lisätiedot

Taulukoiden käsittely Javalla

Taulukoiden käsittely Javalla 1 Taulukoiden käsittely Javalla Mikä taulukko on? Taulukon syntaksi Merkkijonotaulukko Lukutaulukko Taulukon kopiointi 1 Mikä taulukko on? Taulukko on rakenne, minne saadaan talteen usea saman tyyppinen

Lisätiedot

17. Javan omat luokat 17.1

17. Javan omat luokat 17.1 17. Javan omat luokat 17.1 Sisällys Application Programming Interface (API). Pakkaukset. Merkkijonoluokka String. Math-luokka. Kääreluokat. 17.2 Java API Java-kielen Application Programming Interface (API)

Lisätiedot

Ohjelmoinnin perusteet Y Python

Ohjelmoinnin perusteet Y Python Ohjelmoinnin perusteet Y Python T-106.1208 2.3.2009 T-106.1208 Ohjelmoinnin perusteet Y 2.3.2009 1 / 28 Puhelinluettelo, koodi def lue_puhelinnumerot(): print "Anna lisattavat nimet ja numerot." print

Lisätiedot

8. Näppäimistöltä lukeminen 8.1

8. Näppäimistöltä lukeminen 8.1 8. Näppäimistöltä lukeminen 8.1 Sisällys Arvojen lukeminen näppäimistöltä Java-kielessä. In-luokka. In-luokka, käännös ja tulkinta Scanner-luokka. 8.2 Yleistä Näppäimistöltä annettujen arvojen (syötteiden)

Lisätiedot