Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän...

132
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 sovelluk- selle. 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öliitty- mä odottaa käyttäjältä ärsykkeitä (tapahtumia), joihin reagoida. Tämän toteuttaminen nojautuu rin- nakkaisuuden 1 heikompaan muotoon: samanaikaisuuteen säikeitä käyttäen. 1960- 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 (Pa- lo 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 sit- temmin Macintosh-tietokoneiden MacOS-käyttöjärjestelmän, “tavallisten” PC-koneiden Windows- kä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 ohjel- milla on tyypillisesti graafinen käyttöliittymä. Graafisuuden merkitystä ohjelmoinnissa on edelleen nostanut WWW-selaimien mahdollistama yleinen alusta ohjelmille ja selaimiin liittyvä tuki graafi- suudelle. 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än- nö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 pe- rustuu 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 pal- jon kertomuksia ja yleensä niissä mainitaan myös se, että Applen (eräs) perustaja Steve Jobs kävi vierailulla Xerox:n labo- ratorioissa 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” tekstipoh- jaista käyttöliittymää . . . 3 Kunnia X Window:n kehittämisestä annetaan usein MIT:lle. Ensimmäiset versiot valmistuivat hieman 1980-luvun puo- livälin jälkeen. Lähes kaikki nykyisin Unix-tyyppisten käyttöjärjestelmien päällä ajettavat graafiset käyttöliittymät pohjau- tuvat X:ään. 1

Transcript of Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän...

Page 1: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

Luku 1

Johdanto

Tämän oppimateriaalin tarkoituksena on johdatella lukijanykypäivän ohjelmointikieliin liittyväänmoderniin asiakokonaisuuteen: graafiseen käyttöliittymään, erityisesti sen muodostamiseen sovelluk-selle. Graafisen käyttöliittymän toteuttamiseen liittyvät rakenteet eivät ole osa ohjelmointikieltä, vaanne esiintyvät kielen yhteydessä kirjastojen kautta. Perustilassaan sovelluksen graafinen käyttöliitty-mä odottaa käyttäjältä ärsykkeitä (tapahtumia), joihin reagoida. Tämän toteuttaminen nojautuu rin-nakkaisuuden1 heikompaan muotoon: samanaikaisuuteen säikeitä käyttäen. 1960- ja 1970-luvuilla (jalaajasti myöhemminkin) ohjelmointi on tarkoittanut lähinnä keskusmuistissa olevan (ja tiedostoistakeskusmuistiin tuodun) tiedon käsittelyä algoritmisesti. Ohjelmia käytettiin tuolloin eräajon kautta taialkeellisen 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ä kauttagraafinen käyttöliittymä – tuli merkittäväksi vasta paljonmyöhemmin.

Kunnian graafisten käyttöliittymien “keksimisestä” mielletään yleisesti kuuluvan Xeroxin (Pa-lo Alto) tutkimuslaboratorioille (1970-luvun loppu), mutta ensimmäinen graafinen käyttöliittymä oliApplen Lisa-merkkisessä2 tietokoneessa (n. vuonna 1983). Graafiset käyttöliittymätyleistyivät sit-temmin Macintosh-tietokoneiden MacOS-käyttöjärjestelmän, “tavallisten” PC-koneiden Windows-käyttöjärjestelmän (MS) ja X3 Window-pohjaisten käyttöliittymien (X11, Unix) myötä. Nykyisinkäyttöjärjestelmät ovat lähes poikkeuksetta graafisia ja niin myös niiden avulla suoritettavilla ohjel-milla on tyypillisesti graafinen käyttöliittymä. Graafisuuden merkitystä ohjelmoinnissa on edelleennostanut WWW-selaimien mahdollistama yleinen alusta ohjelmille ja selaimiin liittyvä tuki graafi-suudelle.

1Rinnakkaisuus tarkoittaa usean kontrollivirran suorittamista yht’aikaa usean prosessorin toimesta. Samanaikaisuus onrinnakkaisuuden heikompi muoto, jossa vähintäänkin näennäisesti samaanaikaan suoritetaan useita käskyvirtoja — käytän-nössä samanaikaisuus usein toteutetaan yhden laskentayksikön avulla lomittamalla jotenkin käskyvirtojen suoritusta. Javansäikeet perustuvat samanaikaisuuteen. Aidosti rinnakkaisuuteen perustuvia kieliäkin on — niiden voima tyypillisesti pe-rustuu suoritettavien käskyvirtojen tiukasti synkroniseen suoritukseen. Usein rinnakkaisuus “saavutetaan” ohjelmissa ilmanrinnakkaisuuskonstruktioita kielen tasolla; esim. MPI-kirjaston avulla.

2Jostain syystä Xerox:ssa väheksyttiin graafisen käyttöliittymän merkitystä. Lisan käyttöliittymän syntymisestä onpal-jon kertomuksia ja yleensä niissä mainitaan myös se, että Applen (eräs) perustaja Steve Jobs kävi vierailulla Xerox:n labo-ratorioissa vuonna 1979 ja ihastui välittömästi graafiseenkäyttöliittymään. Lisan jälkeen Macintosh-merkkiset tietokoneetjatkoivat graafisten käyttöliittymien voittokulkua — Macit olivat tunnettuja siitä, että niissä ei ollut “tavallista” tekstipoh-jaista käyttöliittymää . . .

3Kunnia X Window:n kehittämisestä annetaan usein MIT:lle. Ensimmäiset versiot valmistuivat hieman 1980-luvun puo-livälin jälkeen. Lähes kaikki nykyisin Unix-tyyppisten käyttöjärjestelmien päällä ajettavat graafiset käyttöliittymät pohjau-tuvat X:ään.

1

Page 2: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

2 LUKU 1. JOHDANTO

1.1 Rakenne

Seuraavaksi tässä oppimateriaalissa tutustutaan AWT:hen(Abstract Windowing Toolkit) ja Swing-kokonaisuuteen. 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 luvussa3. 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 muuta-mat keskeiset apuluokat käsitellään luvussa 5. Ennen siirtymistä Swing-kokonaisuuden käsittelyyn,luvussa 6 pohditaan erilaisia tapoja tehdä GUI-sovellus jaannetaan yleisiä neuvoja sovelluksen teke-miseksi.

AWT-osuuden jälkeen siirrytään käsittelemään AWT:tä osittain syrjäyttämään suunniteltua Swing-kokonaisuutta (joka tuli JDK 1.2:n myötä4). Swing on monessa mielessä parempi kuin AWT, muttakaikki sovellusalustat eivät välttämättä tue Swing:ä (ja lisäksi Swing pohjautuu AWT:hen). Johda-tus Swing-kokonaisuuteen tehdään luvussa 7. Swing-komponentteja on hieman AWT-komponenttejaenemmän ja lisäksi Swing:iin liittyy suurehko joukko hyödyllisiä apuluokkia. Näitä käydään läpi lu-vussa 8.

Tässä vaiheessa GUI-sovellusten tekemiseen liittyvien asioiden pitäisi olla varsin selviä. Toimin-nallisuutta GUI-sovelluksiin saadaan kahdella tapaa: tapahtumankäsittelyn avulla ja itsenäisten säi-keiden avulla (animointi). Luvussa 9.1 kerrotaan säikeiden muodostamiseen ja käyttämiseen liittyvätyleisen asiat. Luku 9 puolestaan pohtii säikeiden käyttöä GUI-sovellusten yhteydessä asiakkaan kan-nalta (animointia, verkkoyhteydet). Javassahan onsynchronized avainsana, jonka avulla voidaan es-tää säikeitä käsittelemästä esim. tiettyjä muuttujia samanaikaisesti. Vaikka säikeet eivät olekaan samaasia kuin rinnakkaisuus5, niin tarkastelemalla samanaikaisuuteen liittyvää problematiikkaa, tarkastel-laan samantapaisia ongelmia kuin mitä liittyy aitoihin rinnakkaisohjelmointikieliin.

Luvuissa 10 tarkastellaan lyhyesti sovellusten tekemistämuihin kuin Javan AWT- ja Swing-kirjastoihinpohjautuen. Kohteina kyseisessä luvussa ovat SWT/JFace, Windows (Win32 API, MFC, C# ja Win-dows Forms) ja ylipäänsä web-selaimeen liittyvät GUI-tekniikat ja Ajax-tekniikat.

Luvussa 11 luonnehditaan hieman ohjelmien tuottamista sovelluskehittimien avulla. Graafistenkäyttöliittymäsovellusten testausta ja dokumentointia tarkastellaan luvussa 12. Loppusanat esitetäänluvussa 13.

4Itse asiassa Swing tuli jo hieman ennen JDK 1.2:ta, osana ns.Java Foundation Classes-kokonaisuutta (JFC).5Rinnakkaisuus 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 si-jaan. Syynä tähän on, että ongelmien ratkaisujen esittäminen rinnakkaisuuden avulla on luonnollisempaa kuin laskennanpakottaminen johonkin mahdollisista peräkkäisistä suoritusjärjestyksistä — tällaisia järjestyksiähän on usein hyvin mo-nia (mielivaltaisesti valittu peräkkäinen järjestys vaikeuttaa ohjelman merkityksen ymmärtämistä). Toinen rinnakkaisoh-jelmoinnin opettamista puoltava seikka on rinnakkaisuuden yleistyminen tietokoneissa. Varsin monissa tietokoneissa (ns.PC-koneissakin) on useita prosessoriytimiä. Lisäksi (transistorien) pakkaustekniikka on kehittynyt viime aikoinahuimasti:Pentium-prosessorien toteuttaminen on vaatinut n. 5-10 miljoonaa transistoria, mutta pakkaustekniikan kehittyminen mah-dollistaa monin(kymmen)kertaisen transistorimäärän sijoittamisen “yhden prosessorin alalle”. Tätä ylimääräistätilaa voikäyttää kahdella tavalla: muistina tai lisäämällä prosessorin rinnakkaisuutta. Uusimmat prosessorit sisältävät lähes poik-keuksetta rinnakkaista suoritusta tukevia piirteitä.

Page 3: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

Luku 2

AWT:n perusteet

AWT (Abstract Windowing Toolkit) on JDK:n useasta paketista koostuva kirjastokokonaisuus graa-fisten 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 muutoksiaGUI:n toiminnan kannalta keskeiseen tapahtumankäsittelyyn. AWT koostuu tapahtumankäsittelyn li-säksi graafisista komponenteista (luokkia, jonka mukaisilla olioilla on graafinen ulkoasu), komponen-tien hierarkiaan ja ikkunointiin liittyvistä luokista sekä useista “apuluokista”, joilla kuvataan erilaisiayksityiskohtia, kuten fontit, värit, komponenttien sijoittelu, jne.

Tavallaan AWT1 on jo myös hieman vanhentunut, sillä JDK 1.2:n (tai oikeammin Java FoundationClasses, JFC) myötä tuli Swing-kokonaisuus2, jonka avulla voidaan tehdä tietyssä mielessä kevyem-piä käyttöliittymiä. Lisäksi Swing tarjoaa AWT:tä enemmängraafisia komponentteja ja Swingin kom-ponentit 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 tekemi-seen perehtyminen kannattaa edelleen aloittaa AWT:stä. Versioiden 1.3 – 1.5 myötä on tullut vainpieniä lisäyksiä.

2.1 AWT ja sovellukset

AWT:llä voi tehdägraafisia sovelluksiaja appletteja(eli sovelmia). Appletit ovat luokkia, jotka peri-vät java.applet .Applet luokasta. Appletit on tarkoitettu suoritettavaksi WWW-selaimen toimes-ta. Graafiset sovellukset puolestaan ovat kuten muutkin Java-sovellukset. Niillä on ’main’-metodi,jota suorittavan tahon (tulkkijava ) toimesta kutsutaan. Graafisuus perustuu luokanjava.awt.Framekäytöön3.

AWT-sovelluksissa ja -appleteissa ohjelmoija luo yhden tai useamman ikkunaolion ja sijoittaakyseisiin ikkunaolioihin graafisia komponentteja, kuten painikkeita, tekstikenttiä ja valitsimia. Itseasiassa “päätason” ikkunaoliot ovat komponettisäiliöitä, joihin voidaan sijoitella “yksinkertaisempia”graafisia olioita. Applettien tapauksessa luokkaApplet edustaa graafisten komponettien säiliötä, mut-ta se tarjoaa myös tärkeän rajapinnan selaimille (mm. metodit ’init’, ’start’, ’stop’, ’update’, joita

1Sunin kerrotaan tehneen AWT:n kuudessa viikossa . . .2Jatkossa puhutaan myös Swing-paketista, vaikka Swing ei ihan tarkkaan ottaen ole Javan paketti (package).3Luonnollisesti 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 taval-lisen pääteyhtyden varassa, suoritusympäristö (komentotulkki) ei välttämättä tue graafisuutta. X:n tapauksessa Unix-kuorenDISPLAY-ympäristömuuttujalla tulee olla oikea arvo.

3

Page 4: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

4 LUKU 2. AWT:N PERUSTEET

selain kutsuu tietyissä tilanteissa). AWT-sovelluksissaei kuitenkaan pelkästään määritellä, mitä kom-ponentteja mihinkin ikkunaan sijoitetaan, vaan erityisillä tapahtumankäsittelyolioilla kerrotaan, mitengraafisten komponenttien tulisi reagoida käyttäjän aiheuttamiin tapahtumiin. Tällaisia tapahtumia ovatmm. hiiren liikuttaminen, “klikkaaminen” ja jonkin näppäimistön merkin painaminen.

Kullakin AWT:n graafisella komponentilla on jokin ulkoasu,johon voi itsekin vaikuttaa. Eri-tyisesti komponentteihin liittyy ’paint’-metodi, joka “tietää”, miten komponentin graafinen ulkoasutuotetaan. Java on käyttöjärjestelmäriippumaton – miten siis graafiset komponentit eri käyttöjärjes-telmien yhteydessä tietävät, miten niiden ulkoasu esitetään? Käytännössä AWT on toteutettu erik-seen kunkin käyttöjärjestelmän alustalle nojautuen niiden tarjoamaan grafiikkakirjastoon. Itse asiassaAWT-komponenttien esitystapa on hieman erilainen eri käyttöjärjestelmissä. Käytännössä toteutta-minen on tehty niin, että kutakin AWT:n komponenttia kohti luodaan kyseisen ikkunointijärjestel-män mukainen graafinen komponentti, ns.vastinolio (peer object), joka suorittaa varsinaisen graafi-sen esityksen. AWT:n komponentteja kutsutaanraskaiksikomponenteiksi juuri tästä syystä – AWT:nkä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ä yh-teys edustavien AWT-komponenttien ja grafiikan toteuttavien komponenttien välillä toteutetaan luo-kan java.awt .Toolkit avulla, mutta sen yksityiskohdista käyttäjän ei tarvitse tietää mitään. VaikkaAWT:n komponenteilla onkin kiinteä yhteys esitystapaan, ohjelmoijan kannalta AWT on käyttöjär-jestelmä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 ainakäytettävissään sama kirjastojen muodostama ohjelmointirajapinta (API = Application ProgrammingInterface).

Koska AWT on alunperin suunniteltu tukemaan MacOS:ää, Windows:a ja X:ää, AWT on pikem-minkin leikkaus kuin unioni kyseisten ikkunointijärjestelmien tarjoamista ominaisuuksista.

2.1.1 AWT:n komponenttien hierarkia

AWT:n GUI-komponenttien juuri on luokkaComponent. Monimutkaisemmat ikkunakomponentitkuten myös graafiset peruskomponentit ovat tämän luokan välittömiä tai välillisiä aliluokkia. Jos ha-lutaan tehdä itse uusia AWT-komponentteja, niin niidenkintulee ollaComponent:n aliluokkia. Ku-vassa 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 kuulu-vat Swing-pakettiin. LuokkaApplet sijaitsee paketissajava.applet , kun muut kuvan 2.1 luokatsijaitsevat paketissajava.awt . Seuraavassa lyhyt selostus kuvan luokista:

Component Graafisten komponenttien perusluokka. Määrittelee graafisten komponenttien perus-ominaisuudet (n. 150 metodia + n. 50 “vanhentunutta” metodia). Kaikkiin komponentteihinliittyy 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 kompo-nentin, jolla on graafinen ulkoasu. Tällä luokalla on useitaaliluokkia: erikoistuneenpia säiliöitä.Luku 5.1.

Page 5: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

2.1. AWT JA SOVELLUKSET 5

Component

Button

Canvas

CheckBox

Choice

Container

Label

List

Scrollbar

TextComponent

Panel

ScrollPane

TextArea

Applet

FileDialog

Window

TextField

Dialog

Frame

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 kutenContainer:iinkin. Paneeliin liittyy ole-tusarvoisestiFlowLayout-komponenttien sijoittelija. Sijoittelutyyliä voi vaihtaa. Paneeli voi ol-la osana jotain toista paneelia (tai muuta ikkunaa). Luku 5.3.

Applet Appletti, eli sovelma, on komponenttien säiliö, joka osaa toimia yhteen selainten kanssa. Eri-tyisesti selain kutsuu tietyssä vaiheessa appletin ’init’-, ’stop’-, ’start’-, ja ’update’-metodeja.Usein appletteja käytetään sijoittelematta komponentteja siihen, vaan vain piirretään appletinpiirtoalueelle. Applettiolion graafinen ulkoasu määritellään ’paint’-metodilla, mutta applettiinsijoitetut graafiset komponentit piirtyvät appletin piirtoalueen päälle. Applettien eräs hallitsevaominaisuus 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 sijoite-tut komponentit vaativat enemmän tilaa X- ja/tai Y-suunnassa kuin mitä säiliön esittämiseen onvarattu.

Window Ikkuna on päätason säiliöluokka, johon ei liity reunoja eikä myös mahdollisuutta liittääsiihen menuvalikoita. Luku 5.2.

Page 6: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

6 LUKU 2. AWT:N PERUSTEET

Dialog Dialogi on luokka, jonka avulla käyttäjälle esitetään yksinkertaisia ikkunoita, joiden tarkoi-tuksena on “kuitata” jokin asia. Esimerkiksi käyttäjälle voidaan kirjoittaa jokin tiedoitus, jonkalukemisen käyttäjän halutaan kuittaavan. Yleisemmin voidaan esittää jokin kysymys, johon onuseita 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 menuvali-koita. Frame on GUI-sovellusten perusluokka – graafiset sovellukset luovat yhden tai useam-man 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ä layout-luokkia (n. 5 luokkaa, luku 5.4), menuvalikoita (luku 5.10)ja ns. popup-valikoita (“ponnahdusvalik-ko”, popup menu, luku 5.11) sekä niiden osia esittäviä luokkia (n. 5 kpl). Edellisten lisäksi AWT:henliittyy suuri määrä “sekalaisia” apuluokkia.

2.2 Yksinkertaisen GUI-sovelluksen resepti

Yksinkertaisimmillaan GUI-sovellus (ei siis appletti) muodostuu ohjelmasta, jossa tehdään yksiFrame-luokan mukainen olio. Kyseiseen olioon sijoitetaan yksinkertaisia graafisia komponentteja sekä mah-dollisesti monimutkaisempia rakenteellisia säiliökomponentteja. Komponentit ovat olioita, joihin liit-tyy graafinen ulkoasu. Kuhunkin säiliökomponenttiin tyypillisesti liitetään säiliön komponenttien si-joittelun 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 lo-puksi luodun graafisen oliokokonaisuuden käsketään myös näkyä graafisesti. Hieman yksinkertais-taen voidaan sanoa, että GUI-sovelluksen ’main’-metodissa (ja siitä kutsuttavissa metodeissa) ei sit-ten juuri muuta tehdäkään – luodaan vain graafisia olioita janiihin liitettäviä tapahtumankäsittely-olioita – tätä kutsutaan graafisen käyttöliittymänalustusvaiheeksi. Erityisesti, ’main’-metodin lopussaei ole silmukkaa, jossa ryhdyttäisiin kuuntelemaan käyttäjän sovellukseen kohdistamia tapahtumia jareagoitaisiin sitten jollakin tavalla kyseisiin tapahtumiin.

GUI-sovelluksen ’main’-metodin lopussa graafista kokonaisuutta tyypillisesti “käsketään” muo-dostamaan graafinen esitys. Tämän jälkeen ’main’-metodi loppuu. Kutsuttaessa sovellusta käyttöjär-jestelmätasolta suoritetaan vain ’main’-metodi — eikö siis ’main’-metodin loppuun tultaessa sovel-luksen 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ä graa-fisen käyttöliittymän luominen luo samalla erillisen säikeen4, 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 ot-taa vastaantapahtumia(event) käyttöjärjestelmätason graafisilta vastinolioita, tunnistaa mihin AWT:nkohdeolioon(event source) kyseinen tapahtuma kohdistuu, jalaukaista (fire) kyseisen kohdeolion

4Miten tämän tarkkaan ottaen tapahtuu, vaikuttaa olevan hyvin varjeltu “salaisuus”. Suurehko kirjajoukko ei tunnu osaa-van antaa vastausta siihen, missä ja minkä toimesta kyseinen säie luodaan. Vaihtoehtoja lienee kaksi: joko GUI-sovelluksenpääikkunan (Frame-olion) luonti luo sen tai sitten kyseinen säie liittyy jotenkin GUI-sovelluksen päätason toteuttavaanvastinolioon (peer) . . .

Page 7: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

2.3. ESIMERKKEJÄ 7

sellaiset tapahtumankäsittelijät, jotka voivat käsitellä kyseisen tapahtuman. Kohdeoliohin (siis graa-fisiin komponentteihin) on voinut kiinnittyä erilaisiin tapahtumiin reagoiviatapahtumankäsittelijöi-tä. Tapahtumankäsittelijät ovat yksinkertaisesti olioita,joiden ainoa tehtävä on osata suorittaa tiettymetodi tai tietyt metodit. Käyttöjärjestelmän kannalta tapahtumankäsittelijät edustavat ns.call back-funktioita. Tapahtumankäsittelijöitä kutsutaan myöstapahtumankuuntelijoiksi(event listener). Huo-maa, että yksi tapahtuma voi laukaista useita tapahtumankäsittelijöitä. Lisäksi tapahtuma saattaa saadakomponentin laukaisemaan jonkin tapahtuman. Edellisen perusteella pitäisi olla ymmärrettävää, ettäJavan tapahtumankäsittelymallia kutsutaan delegointiinperustuvaksi. Yleisesti tällaista tapahtuman-käsittelijöiden muodostamiseen perustuvaa ohjelmointiakutsutaantapahtumaohjatuksi ohjelmoinnik-si (event-driven programming).

Tapahtumat ovat jonkinEventObject-luokan5 (AWT:n tapauksessaAWTEvent-luokan (joka onEventObject:n aliluokka)) aliluokan mukaisia olioita. Tällaisen aliluokan nimi on muotoa XXXEvent,esim. MouseEvent tai KeyEvent. Tapahtumankäsittelijät puolestaan ovat jonkinEventListener-luokan 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 4.

2.3 Esimerkkejä

Seuraavassa kolme esimerkkiä graafisista sovelluksista: 2applettia ja 2 itsenäistä GUI-sovellusta.

2.3.1 HiWorld-appletti

Esimerkissä 2.1 on “klassinen” tervehdyksen tulostava appletti. Appletti lukee tulostettavan terveh-dyksen HTML-tiedostosta PARAM-määren välityksellä, tulostaa sen keltaista taustaa vasten keskite-tysti 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 tus-kin graafista käyttöliittymää, sillä siihen ei liity muuta toiminnallisuutta kuin tervehdyksen tulostami-

5On olemassa myös luokkaEvent, mutta se liittyy JDK 1.0:aan.

Page 8: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classHiWorldApplet extendsApplet {

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

Page 9: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

2.3. ESIMERKKEJÄ 9

2.3.2 TODO-lista

Seuraava esimerkki 2.2 on GUI-sovellus, joka muodostaa yhden kehysolion ja sijoittelee siihen kaksiGUI-komponenttia: päiväyksen sisältävän tekstin ja pudotusvalikon, joka edustaa ’ToDo’-asioidenlistaa. 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 classToDo {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 =newLabel(new SimpleDateFormat().format(new Date()),

Label.CENTER);kehys.add(l, BorderLayout.NORTH);File f = newFile(tdsto);if (!f.exists()) {

kehys.add(new Label("Nothing to do!" ), BorderLayout.CENTER);kehys.setVisible(true); return ;

}// Tiedosto olemassa. Luetaan ToDo-lista.BufferedReader fin =newBufferedReader(new

InputStreamReader(new FileInputStream(f)));Choice lista =newChoice();String rivi;while ((rivi = fin.readLine())6= null )

if (!rivi.trim().equals("" )) lista.add(rivi.trim());// Suljetaan tiedosto.fin.close();kehys.add(lista, BorderLayout.CENTER);kehys.pack(); kehys.setVisible(true);

}}

Page 10: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

10 LUKU 2. AWT:N PERUSTEET

Kuva 2.3: Graafinen ToDo-sovellus (ei appletti).

Graafinen ToDo-sovellus on toiminnaltaan hyvin suoraviivainen. Metodin ’main’ aluksi luodaanFrame-tyyppinen kehysolio, johon sovelletaan heti määrittelyitä: asetaan koko, sijoittelumanageri jataustan väri. Kehyksen otsikko on määritelty jo luonnin yhteydessä. Tämän jälkeen luodaan teks-tileimaolio, jonka sisällöksi “lasketaan” käynnistyshetken mukainen päiväys. Kyseinen leima laite-taan osaksi kehystä. Tämän jälkeen avataan vakioniminen (ToDo.txt ) tekstitiedosto, josta on tar-koitus 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, mut-ta 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 mini-miin (’pack’-metodin kutsu) ja kehyksestä tehdään näkyvä,eli se piirretään ikkunointijärjestelmäntoimesta.

Sovellukseen voi kohdistaa muutamia toimenpiteitä. Sovelluksen luomaa ikkunaa voi siirtää pai-kasta toiseen, ikkunan kokoa voi muuttaa ja valikossa olevaa valintaa voi vaihtaa. Päiväystä edustavaatekstiä ei voi muuttaa. Oikeastaan tämä sovellus ei kuitenkaan reagoi juuri mihinkään tapahtumaan:valinnan vaihtamisella voisi olla jokin seuraus sovelluksen toiminnan kannalta. ToDo-sovellukseenei liity yhtään itse tehtyä tapahtumankäsittelijää — tälläon erityisesti se ikävä seuraus, että ikkunansulkeminen ei onnistu! Yritys sulkea sovellus kyllä generoi tietynlaisen tapahtuman, mutta mikääntapahtumankäsittelijä ei ota sitä kiinni. Ikkunan sulkeminen onnistuu vain lopettamalla itse sovellusbrutaalisti6.

2.3.3 Päivän mietelause

Seuraava esimerkki 2.3 näyttää luokan Mietelause. Luokka Mietelause on GUI-sovellus, joka tietyn-laisesta “mietelauseita” sisältävästä tiedostosta arpooyhden ja esittää sen GUI-sovelluksen ikkunassa,ks. kuva 2.4.

Tähän sovellukseen liittyy yksi tapahtumankäsittelijä, joka toteutetaan sisäluokalla IkkunaKuun-telija. Kyseisen luokan rungossa toteutetaan vain yksi metodi, jonka seurauksena ikkuna — johontä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. LuokkaMietelause perii luokanFrame ja on siten uudenlainen säiliöluokka. Nyt ’main’-metodi onhyvinsuppea ja sen tarkoitus on vain luoda kehysolio, johon komponentit sitten sijoitetaan. Koko GUI-sovelluksen alustusosa tehdään kehysolion luontimetodissa — tästä syystä ’main’-metodissa vain luo-daan kehysolio (ottamatta sitä edes talteen mihinkään muuttujaan). Luontimetodissa ’Mietelause(String

6Esim. ctrl-C annettuna ikkunalle, josta sovellus käynnistettiin, on monissa ympäristöissä toimiva ratkaisu . . .

Page 11: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

2.3. ESIMERKKEJÄ 11

t)’ tehdään hyvin samanlaisia asioita kuin esimerkin 2.2 ’main’-metodissa. Nyt kuitenkin kehyksenmetodeja voidaan kutsua suoraan, koska Mietelause on itse määritelty kehys. Toinen erikoisuus on ke-hykseen liitettävän ikkunatapahtumien kuuntelijan luominen (’new IkkunaKuuntelija()’) ja liittämi-nen kehysolioon (’addWindowListener(. . . )’). Luokan Mietelause konstruktori on liian pitkä ja kon-struktorin 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 classMietelauseextendsFrame {private String tdsto;private static String fixed_tdsto ="Mietelauseet.txt" ;

public static void main(String[] args)throws Exception {if (args.length> 0) newMietelause(args[0]);else newMietelause();

}

public Mietelause()throws Exception{ this(fixed_tdsto); }

jatkuu . . .

Page 12: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 = newFile(tdsto);if (!f.exists()) {

add(new Label("Mietetiedostoa " +tdsto+"ei ole!" ));return ;

}// Tiedosto olemassa. Luetaan miete.BufferedReader fin =newBufferedReader(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())6= null )if (rivi.trim().equals("" )) break;

// Seuraavaksi vuorossa on tulostettava miete.while ((rivi = fin.readLine())6= null )

if (rivi.trim().equals("" )) break;elseadd(new Label(rivi, Label.LEFT));

// Suljetaan tiedosto.fin.close();pack();setVisible(true);

}

classIkkunaKuuntelijaextendsWindowAdapter {public void windowClosing(WindowEvent e) {

// Ikkunaa ollaan sulkemassa.e.getWindow().dispose();System.exit(0);

}}

}

Page 13: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

2.3. ESIMERKKEJÄ 13

2.3.4 Henkilöiden esittelyappletti

Tämän kohdan viimeinen esimerkki 2.4 on appletti, johon on liitetty 5 graafista komponenttia. Kak-si komponenteista ovat nappuloita, joihin kumpaankin on liitetty tapahtumankäsittelijäolio. Appletintehtävänä on lukea tiedostosta henkilöitä koskevia tietoja: kullakin rivillä on pilkulla erotettuna hen-kilön status, nimi ja sen tiedoston URL, jossa on henkilön kuva7.

Appletin ’init’-metodia kutsutaan appletin lataamisen jälkeen selaimen toimesta, ja sen tehtävänäon luoda tarvittavat graafiset komponentit. Aluksi luodaanedellinen- ja seuraavanappulat, henkilönstatusta ja nimeä esittävät leimat ja piirtopinta (itse määritelty luokka HenkilonKuva), jonka avul-la esitetään henkilön (gif-)kuva. Edellisiin olioihin sovelletaan määrittelyitä ja ne sijoitetaan appletinsäiliöön (’add’-metodin kutsuilla). Sitten nappuloihin liitetään tapahtumankäsittelijät ja lopuksi ver-kon kautta ladataan tiedostossa olevat henkilöiden tiedotja sitä kautta henkilöiden kuvat. Henkilöidentiedot muodostavat periaatteessa kaksisuuntaisen listan, ja appletilla on mahdollista kulkea tässä lis-tassa 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öntiedot leimojen ja piirtopinnan arvoiksi.

Nappuloihin liitettyjen kuuntelijoiden idea on vain päivittää tietoa, jonka perusteella tiedetään ku-ka 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 uudel-leen).

Huomaa, miten henkilön status- ja nimitietoja esittävät leimat talletetaanPanel-tyyppiseen säili-öön. Metodissa ’päivitäTiedot’ kyseiset leimaoliot poistetaan säiliöstä ja niiden tilalle laitetaan uuttahenkilöä vastaavat uudet8 leimaoliot. Huomaa myös, miten helppoa kuvan piirtäminen on Canvas:stajohdetulle HenkilonKuva-tyyppiselle piirtopinnalle, kunhan ensin verkosta haettujen (gif-)kuvien pe-rusteella on muodostettu kuvia esittävätImage-tyyppiset oliot.

Seuraavassa esimerkki HTML-tiedostosta, jolta esimerkin2.4 appletti voidaan käynnistää.

<html><title> Henkilökunnan esittely appletilla </title><body>

<APPLETcode=StaffApplet.classwidth=410height=500><PARAM NAME=Tiedosto

VALUE="http://www2.cs.utu.fi/opinnot/kurssit/APSK/e simerkit/henkilot.txt"></APPLET>

</body></html>

7Kuvien tulee olla samalla palvelimella, minne appletti sijoitetaan.8Itse asiassa vanhoja leimaolioita ei olisi tarvinnut poistaa. Niiden sisältöä olisi ollut mahdollista muuttaa.

Page 14: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classStaffAppletextendsApplet {private static final int MAXLKM = 200;private String[] tittelit = new String[MAXLKM];private String[] nimet =newString[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 =newButton("Seuraava" );eNappula =newButton("Edellinen" );kuva =newHenkilonKuva();henkilönNimi =newLabel("Henkilön nimi" , Label.LEFT);henkilönStatus =newLabel("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);// KuuntelijatsNappula.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

= newBufferedReader(new InputStreamReader(tiedostovirta));String rivi;int i = -1;

Page 15: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

2.3. ESIMERKKEJÄ 15

Esimerkki 2.4. StaffApplet (jatkoa).

jatkuu . . .while ((rivi = tiedosto.readLine())6= null ) {

i++;StringTokenizer st =newStringTokenizer(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 =newLabel(nimet[nykyinen], Label.LEFT);henkilönStatus =newLabel(tittelit[nykyinen], Label.RIGHT);henkilö.add(henkilönNimi, BorderLayout.WEST);henkilö.add(henkilönStatus, BorderLayout.EAST);henkilö.validate();kuva.repaint();

}

classHenkilonKuvaextendsCanvas {public void paint(Graphics g) {

if (kuvat[nykyinen] ==null )showStatus("Henkilöllä " +nykyinen+"ei kuvaa." );

elseg.drawImage(kuvat[nykyinen], 0, 0,this);}

}

jatkuu . . .

Page 16: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

16 LUKU 2. AWT:N PERUSTEET

Esimerkki 2.4. StaffApplet (jatkoa).

jatkuu . . .

classSeuraavaKuuntelijaimplementsActionListener {public void actionPerformed(ActionEvent e) {

nykyinen++;if (nykyinen≥ lkm) nykyinen = lkm-1;päivitäTiedot();

}}

classEdellinenKuuntelijaimplementsActionListener {public void actionPerformed(ActionEvent e) {

nykyinen--;if (nykyinen< 0) nykyinen = 0;päivitäTiedot();

}}

}

Page 17: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

Luku 3

AWT:n peruskomponentteja

Tässä luvussa jatketaan AWT:hen tutustumista. AWT:n komponenteista käsitellään ensin kaikkienkomponenttien yliluokkaComponent luvussa 3.1. Vastaavasti kaikkien säiliöiden yliluokkaCon-tainer käsitellään luvussa 5.1. Näiden kahden luokan tarkoitus onlähinnä määritellä, mitä yleisiäominaisuuksia on kaikilla graafisilla komponenteilla ja säiliöillä.

Komponenttien yleisten ominaisuuksien jälkeen perehdytään suhteellisen yksinkertaisiin kompo-nentteihin: 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

LuokkaComponent onabstraktiyliluokka kaikille graafisille komponenteille. Sen tehtävänä on mää-ritellä komponenttien yhteiset ominaisuuden määrittelemällä joukko metodeja. VaikkaComponentonkin abstrakti luokka, niin mikään sen metodeista ei ole abstrakti. LuokkaComponent toteuttaaseuraavien rajapintojen kaikki metodit:ImageObserver, MenuContainer ja Serializable (ei mi-tään toteutettavaa). Alle on poimittu joitakin luokanComponent n. 150 metodista1. Metodit voineeryhmitellä seuraaviin osiin: tapahtumankäsittely, komponettiin liittyvien määrittelyiden asettaminenvoimaan (väri, näkyvyys, fontti, . . . ) ja edellisten havainnointi. Lisäksi on vielä joitakin sekalaisiametodeja (ja runsaasti vanhentuneita metodeja).

Kaikkiin komponentteihin voi yleisesti kohdistua useanlaisia tapahtumia. Näitä ovat mm. seuraa-vat

komponettitapahtumat LuokkaComponentEvent. Tapahtuman syynä on komponentin kokoon tainäkyvyyteen kohdistunut muutos.

fokusointitapahtumat Luokka FocusEvent; syynä on fokuksen (minne näppäimistön merkit koh-distuvat) saaminen tai menettäminen.

näppäintapahtumat Luokka KeyEvent; tapahtuma johtuu näppäimistön merkin painamisesta (taivapauttamisesta).

hiiritapahtumat LuokkaMouseEvent; tapahtuman syynä on se, että hiirellä on tehty jotain: siirret-ty, klikattu, hiiren osoitin on siirtynyt komponentin alueelle, . . .

1Lisäksi on n. 50 metodia, joita ei ole enää tarkoitus käyttää(yhteensopivuutta JDK 1.0:n suuntaan).

17

Page 18: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

18 LUKU 3. AWT:N PERUSKOMPONENTTEJA

Komponentteihin voi kohdistua muitakin yleisiä tapahtumia. Tapahtumia ja niiden käsittelyä käsitel-lään perusteellisesti luvussa 4.

void addComponentListener(ComponentListener l )Lisää komponenttitapahtumien kuuntelijanl komponenttiin.

void removeComponentListener(ComponentListener l )Poistaa komponenttitapahtumien kuuntelijanl komponentista.

void addFocusListener(FocusListener l )Lisää fokusointitapahtumien kuuntelijanl komponenttiin.

void removeFocusListener(FocusListener l )Poistaa fokusointitapahtumien kuuntelijanl komponentista.

void addKeyListener(KeyListener l )Lisää näppäintapahtumien kuuntelijanl komponenttiin.

void removeKeyListener(KeyListener l )Poistaa näppäintapahtumien kuuntelijanl komponentista.

void addMouseListener(MouseListener l )Lisää hiiritapahtumien kuuntelijanl komponenttiin.

void removeMouseListener(MouseListener l )Poistaa hiiritapahtumien kuuntelijanl komponentista.

void addMouseMotionListener(MouseMotionListener l )Lisää hiiriliiketapahtumien kuuntelijanl komponenttiin.

void removeMouseMotionListener(MouseMotionListener l )Poistaa hiiriliiketapahtumien kuuntelijanl 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ää tapahtumane komponentille.

protected void processEvent(AWTEvent e)Prosessoi komponenttiin kohdistuvan tapahtumane.

void add(PopupMenu p)Lisää ponnahdusmenunp 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ääl-lä, sillä onCursor-luokan olion määräämä graafinen ulkoasu).

Font getFont()Palauttaa komponenttiin liitetyn fontin.

Graphics getGraphics()Palauttaa komponentin graafisen ulkoasun.

int getHeight()

Page 19: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 )

Asettaac :n komponentin taustaväriksi.void setForeground(Color c )

Asettaac :n komponentin pääväriksi.void setFont(Font f )

Asettaa komponentin tekstifontiksif :n.void setSize(Dimension d)

Asettaa komponentin uudeksi kooksid:n.void setBounds(int x , int y , int dx , int dy )

Asettaa komponentin uudeksi kooksidx × dy:n ja sijoittaa komponentin kohtaan(x, y).void paint(Graphics g)

Piirtää komponentin piirtoalueelleg.void repaint()

Piirtää komponentin uudelleen.void requestFocus()

“Vaatii” (näppäimistö)fokusta komponentille.void setCursor(Cursor c )

Asettaa komponentin uudeksi kursorikuvioksic :n.void setEnabled(boolean b)

Josb = true, komponenttiin voidaan kohdistaa käyttäjän toimenpiteitä. false: ei voida (kompo-nentti on “harmaa”).

void setVisible(boolean b)Josb = 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 tullaheti voimaan muutenkin, käytetään lähinnä luokanContainer yhteydessä.

Page 20: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

20 LUKU 3. AWT:N PERUSKOMPONENTTEJA

3.2 Nimike Label

Nimikeeli tekstileimaon graafinen komponenttiLabel, joka esittää määritellyn sisältöistä tekstiä mää-ritellyllä fontilla (ja koolla). Tekstiä ei pääse muuttamaan käyttöliittymässä editoimalla siihen jonkinuuden sisällön, mutta tekstiä voi kyllä muuttaa ohjelman toimesta, kutsumalla tekstileimaolion meto-dia ’setText’. Leimaolioon liittyy myös tieto tekstin kohdistuksesta, jonka mahdollisia arvoja ovatLa-bel.LEFT (vasempaan reunaan),Label.RIGHT (oikeaan reunaan) jaLabel.CENTER (oikeaan reu-naan). Oletuksena on vasen reuna. Jonkin muun kohdistuskoodin (kokonaisluku) antaminen nostaapoikkeuksen (IllegalArgumentException). LuokanLabel tapa koodata luokkavakioiden avulla väli-tettäviä laillisia parametriarvoja edustaa yleisesti sovellettua tapaa AWT:ssä. LuokanLabel metodejaesitellään taulukossa 3.1. Näiden lisäksi ovat luonnollisesti luokastaComponent perityt metodit.

Label()Konstruktori, joka alustaa sisällön arvoksi tyhjän tekstin.

Label(String t )Konstruktori, joka alustaa sisällöksit :n.

Label(String t , int align )Konstruktori, joka alustaa sisällöksit :n jaalign määrittää tekstin kohdistuksen.

String getText()Funktio, joka palauttaa leiman sisältämän merkkijonon.

void setText(String t )Asettaa leiman uudeksi sisällöksit :n.

int getAlignment()Palauttaa tekstin kohdistuksen ilmaisevan arvon.

void setAlignment(int align )Asettaa kohdistuksen uudeksi arvoksialign:n.

Taulukko 3.1: LuokanLabel konstruktoreita ja metodeja.

Esimerkki 3.1 havainnollistaa leimoja ja niiden metodeja.Sovelluksessa luodaan kolme leimaakäyttäen eri konstruktoreita. Ks. kuva 3.1. Leimoihin liitetään erilaiset fontit ja erilainen sisältö. Nelisätään kehykseen ja kuhunkin leimaan liitetään kuuntelija, joka syklisesti kierrättää kohdistuksen ar-voa, kun leimaa klikataan. Kehykseen liitetään tavanomainen ikkunan sulkeva tapahtumankäsittelijä.

Kuva 3.1: Graafinen LeimaTesti-sovellus.

Page 21: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classLeimaTesti {

public static void main(String[] args) {Font ss18 =new Font("SansSerif" , Font.PLAIN, 18);Font tr14 =new Font("TimesRoman" , Font.PLAIN, 14);Font trb14 =newFont("TimesRoman" , Font.BOLD, 14);Label l1 =newLabel("Ensimmäinen teksti" , Label.LEFT);Label l2 =newLabel("Toinen teksti" );l2.setAlignment(Label.CENTER);Label l3 =newLabel(); l3.setAlignment(Label.RIGHT);l3.setText("Kolmas teksti" );

Frame f =newFrame("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

classLeimanKuuntelijaextendsMouseAdapter {

public void mouseClicked(MouseEvent e) {// Tiedetään, että kohde on jokin leimoista.Label l = (Label)e.getSource();int uusi = 0;switch (l.getAlignment()) {

caseLabel.LEFT: uusi = Label.CENTER;break;caseLabel.CENTER: uusi = Label.RIGHT;break;caseLabel.RIGHT: uusi = Label.LEFT;break;

}l.setAlignment(uusi);

}} // LeimanKuuntelija

Page 22: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

22 LUKU 3. AWT:N PERUSKOMPONENTTEJA

3.3 PainikeButton

Painike eli nappula on hieman kuten tekstileima: nappulan sisältönä on tekstiä. Nappulan tekstinvoi vaihtaa toiseksi, mutta leiman tapaan, nappulan tekstiin ei liity mahdollisuutta kohdistaa se jom-paan kumpaan reunaan. Nappulan ehkä tärkein ominaisuus onActionEvent-tyyppisen tapahtumanluomisen mahdollisuus. Kun nappulaa klikataan hiirellä, se tuottaaActionEvent-tyyppisen tapahtu-man. Nappulaan on mahdollista liittääComponent-luokassa määriteltyjen tapahtumankäsittelijöidenlisäksi myösActionListener-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öksit :n.

String getLabel()Funktio, joka palauttaa nappulan tekstisisällön.

void setLabel(String t )Asettaa nappulan tekstisisällöksit :n.

void addActionListener(ActionListener l )Liittää nappulaanActionListener-tyyppisen tapahtumankäsittelijänl .

void removeActionListener(ActionListener l )Poistaa nappulaan liitetyn tapahtumankäsittelijänl .

void setActionCommand(String c )Kun ActionEvent laukeaa, siihen liittyy tieto “komennon” nimestä. Tällä metodilla nimen voiasettaa. Oletusarvo on nappulan nimi.

String getActionCommand()Palauttaa “komennon” nimen.

Taulukko 3.2: LuokanButton 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äänluotuun kehykseen ja kuhunkin liitetään kuuntelija, joka kuunteleeActionEvent:jä. Neljä ensimmäis-tä nappulaa operoivat viidennen nappulan tilalla: kaksi ensimmäistä poistavat tai mahdollistavat vii-dennen klikkaamisen. Toiset kaksi piiloittavat tai tuovatnäkyviin nappulan. Kun viides nappula onklikattavissa ja sitä klikataan, kasvaa tekstikentässä olevan laskurin arvo. Esto-nappulaan on liitettymyös hiiren klikkauksia kuunteleva käsittelijä, joka klikkauksen seurauksena vähentää laskurin arvoayhdellä. Tuolloin nappulaan liittyy kaksi erilaista tapahtumankäsittelijää, jotka kummatkin suoritetaannappulaa klikattaessa.

Page 23: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

3.3. PAINIKEBUTTON 23

Esimerkki 3.2 GUI-sovellus, joka demonstroi nappuloita.

import java.awt.∗;import java.awt.event.∗;

public classNappulaTesti {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 =newFrame("Nappulatesti" );f.setLayout(new GridLayout(0,2));b1 =newButton("Mahdollista" );b2 =newButton("Estä" );b3 =newButton("Piilota" );b4 =newButton("Esiin" );b5 =newButton("Lisää laskuria" );f.add(b1); f.add(b2); f.add(b3); f.add(b4); f.add(b5); f.add(lt);NappulaKuuntelija nk =newNappulaKuuntelija();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 classNappulaKuuntelijaimplementsActionListener {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 classB2KuuntelijaextendsMouseAdapter {public void mouseClicked(MouseEvent e) {

laskuri--; piirräLaskuri();}

} // B2Kuuntelija} // NappulaTesti

Page 24: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

24 LUKU 3. AWT:N PERUSKOMPONENTTEJA

3.4 PiirtokangasCanvas

Piirtokangas, luokkaCanvas, on vain “tyhjä” alue, jolle voidaan piirtää ja/tai sijoittaa kuva. Aiemminesimerkissä 2.4 luotiin uusi luokka HenkilonKuva perimällä luokastaCanvas. Tämä on tyypillinentapa käyttää piirtokangasta: määritellään perinnän yhteydessä metodi ’paint’ uudelleen. LuokanCan-vas määrittelemiä metodeja on esitetty taulukkossa 3.3 — metodeja on hyvin vähän, piirtokangasperii lähes kaikki ominaisuutensa suoraan luokaltaComponent.

Canvas()Konstruktori, joka luo tyhjän piirtokankaan. Kankaan koonvoi asettaaComponent:sta peri-tyillä metodeilla.

void addNotify()Luo vastinolion.

void paint(Graphics g)Piirtää kankaang:hen. Kutsutaan lähinnä ’repaint’:n toimesta.

Taulukko 3.3: LuokanCanvas konstruktoreita ja metodeja.

3.5 PudotusvalikkoChoice

Pudotusvalikkoon eräänlainen valintalista. Listassa voi olla useita tekstialkioita, joista käyttäjä voi va-lita yhden. Ainoastaan valittu tekstialkio on näkyvissä. Painaessaan pudotusvalikkoa (tai sen reunassaolevaa “nuolta”), pudotusvalikko avautuu ja klikkaamallavoidaan jokin alkioista valita. Valinnan suo-rittamisen jälkeen pudotusvalikko “sulkeutuu” automaattisesti. Jos pudotusvalikon valintalista on niinpitkä, ettei se mahdu näytölle, tulee pudotusvalikon reunalle automaattisesti liu’utin.

Nappulan tavoin pudotusvalikkoon liittyy erityinen tapahtuma ItemEvent ja mahdollisesti senmukaisia tapahtumankäsittelijöitä (itse tehtyinä ja komponenttiin ohjelmassa liitettyjä), joiden yli-luokkana toimiiItemListener. Tapahtumia voi periaatteessa olla kahdenlaisia: jokin tekstialkio tuleevalituksi tai valinta tulee poistetuksi. LuokanItemListener perijöiden tarvitsee toteuttaa vain yk-si metodi: ’void itemStateChanged(ItemEvent e)’. Luokalla ItemEvent on metodi ’getItemSelec-table()’, jolla saadaan selville tapahtuman kohdeolio. Metodi ’getItem()’ kertoo valitun alkion. Me-todilla ’getStateChange()’ saadaan selville onko kysymysvalinnasta (vakioItemEvent.SELECTED)vai valinnan poistosta (vakioItemEvent.DESELECTED). Ilmeisesti pudotusvalikko generoi vain va-lintatapahtumia. 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 teks-tikenttää appletille. Pudotuslistaan kiinnitetään tapahtumankäsittelijä, joka raportoi valinnan suorittamis-ja poistamistapahtumista. Ks. kuva 3.3.

Page 25: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

3.5. PUDOTUSVALIKKOCHOICE 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 valintalistani :nnen alkion.

void add(String item )Lisääitem :n valintalistan loppuun. Jositem == null, nostetaanNullPointerException.

void insert(String item , int i )Lisää (korvaamatta)item :n kohtaani . PoikkeusIllegalArgumentException, jos i < 0.

void remove(String item )Poistaa valintalistasta ensimmäisenitem :n esiintymän.

void remove(int i )Poistaa valintalistan alkion positiostai .

void removeAll()Tyhjentää valintalistan.

String getSelectedItem()Palauttaa valitun alkion valintalistasta. Palauttaanull, jos ei mitään valittuna.

int getSelectedIndex()Palauttaa valitun alkion indeksinumeron (-1, jos ei mitäänvalittu).

void select(String str )Valitsee listasta ensimmäisen alkion, jonka sisältö sama kuin str .

void addItemListener(ItemListener l )Lisää pudotusvalikkoonItemListener-käsittelijänl .

void removeItemListener(ItemListener l )PoistaaItemListener-käsittelijänl .

Taulukko 3.4: LuokanChoice konstruktoreita ja metodeja.

Kuva 3.3: ChoiceTesti-appletti.

Page 26: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

26 LUKU 3. AWT:N PERUSKOMPONENTTEJA

Esimerkki 3.3 Appletti, joka demonstroi pudotusvalikkoja.

import java.awt.∗;import java.awt.event.∗;import java.applet.∗;

public classChoiceTestiextendsApplet{private TextField tf_in =newTextField(20);private TextField tf_out =newTextField(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

classListaKuuntelijaimplementsItemListener {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 TekstikomponentitTextComponent ja TextField sekäTextArea

Seuraavaksi käsitellään tekstikomponentit, joiden sisältöä voi mm. editoida (lisätä ja tuhota haluttujamerkkejä). Tekstikomponentin sisällöstä voi myös valita haluamansa osan. Tekstikomponentteja onkahdenlaisia: yksi- (TextField) ja monirivisiä (TextArea). Kummankin yliluokkana toimii yhteisetominaisuudet määrittäväTextComponent-luokka2. Valinnan ja sisällön editoinnin lisäksi tekstikom-ponenttien yhteiset ominaisuudet käsittelevät sisällön ohjelmallista käsittelyä, valitun tekstin tutki-mista ja valintaa, tekstisisällön editoitavuuden määrittelyä (kun sisältö ei ole editoitavissa, se muuttuu“harmaaksi”), kohdistimen position määrittelemistä ja erityisenTextListener-tyyppisen tapahtuman-

2Vaikka tämä luokka on konkretti, virallisesti sillä ei ole konstruktoria (JDK:n dokumentaatio). Toisaalta, ainakin JDK1.1:ssä luokassa esiintyy yksi konstruktori.

Page 27: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

3.6. TEKSTIKOMPONENTITTEXTCOMPONENT JA TEXTFIELD SEKÄ TEXTAREA 27

käsittelijän liittämistä ja poistamista. Tapahtumankäsittelijä käsitteleeTextEvent-tyyppisen tapahtu-man — kyseinen tapahtuma syntyy, kun tekstin sisältö muuttuu. Huomaa, että fontin asettamista eimääritellä tässä luokassa, vaan se peritään luokastaComponent. Taulukossa 3.5 on lueteltu joitakinhyödyllisiä metodeja.

void setText(String t )Asettaa sisällöksi merkkijonont .

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, josb == true (oletus), muutoin ei.

void select(int s , int e)Valitsee tekstin positiostas positioone.

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.PoikkeusIllegalArgumentException, jos laiton positio.

int getCaretPosition()Palauttaa kohdistimen position.

void addTextListener(TextListener l )LisääTextListener-tyyppisen tapahtumankuuntelijanl .

void removeTextListener(TextListener l )Poistaa tapahtumankäsittelijänl .

Taulukko 3.5: LuokanTextComponent metodeja.

3.6.1 Luokka TextField

Luokka TextField perii kaikki luokanTextComponent (kuten myös luokanComponent) metodit.LuokanTextField oliot edustavat yksirivistä editoitavissa olevaa tekstiä. Komponentilla on koko, jo-ka voidaan asettaa konstruktorien yhteydessä tai erikseenmetodilla ’setColumns’. Tämä koko on vainteksti-ikkunan koko, se ei ole raja syötettävien merkkien lukumäärälle. Kokoa voidaan helposti muut-taa (esim. muuttamalla ikkunan kokoa). Tähän tekstikomponenttiin liittyy myös mahdollisuus määrit-tää 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äyt-täjän kirjoittamat merkit. Tällainen toiminnallisuus on tarpeellinen mm. salasanoja kirjoitettaessa.LisäksiTextField-tyyppisiin komponentteihin voi liittääActionListener-tyyppisen tapahtumankäsit-telijän (tai useita niitä). Tällaiset käsittelijät kuuntelevatActionEvent-tyyppisiä tapahtumia: sellainen

Page 28: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

28 LUKU 3. AWT:N PERUSKOMPONENTTEJA

kohdistuu tekstikomponenttiin, kun käyttäjäpainaa enter:ä. Taulukossa 3.6 on joitakin hyödyllisiämetodeja — LuokallaTextField on myös useita muita metodeja tekstikentän “fyysisen” koonmäärit-tämiseksi.

TextField()Konstruktori, joka alustaa sisällön arvoksi tyhjän tekstin.

TextField(String t )Konstruktori, joka alustaa sisällöksit :n.

TextField(int cols )Konstuktori, joka alustaa sisällön tyhjäksi ja asettaa tekstikentän kooksicols .

TextField(String t , int cols )Konstruktori, joka alustaa sisällöksit :n ja kooksicols .

int getColumns()Palauttaa tekstikentän koon.

void setColumns(int cols )Asettaa tekstikentän kooncols :ksi. PoikkeusIllegalArgumentException, joscols < 0.

char getEchoChar()Palauttaa erityisen kaiutusmerkin.

void setEchoChar(char c )Asettaac :n erityiseksi kaiutusmerkiksi. Normaalitilaan palataan, kunc :llä arvo 0.

boolean echoCharIsSet()Käytetäänkö erityistä kaiutusmerkkiä kirjoitettavien merkkien tilalla.

void addActionListener(ActionListener l )Liittää komponenttiinActionListener-tyyppisen tapahtumankäsittelijänl .

void removeActionListener(ActionListener l )Poistaa komponenttiin liitetyn tapahtumankäsittelijänl .

Taulukko 3.6: LuokanTextField konstruktoreita ja metodeja.

Kuva 3.4: OsoiteTaltioija-sovellus, jolla voi taltioida osoitetietoja tiedostoon.

Esimerkissä 3.4 toteutetaan yksinkertainen sovellus, jonka avulla voi taltioida osoitetietoja (teks-timuodossa) tiedostoon. Sovellus esittää lomaketta, jonka tekstikentät käyttäjä voi täyttää. Kehykseenliitettyjen 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 so-vellukselle komentoriviparametrina. Ks. kuva 3.4.

Page 29: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

3.6. TEKSTIKOMPONENTITTEXTCOMPONENT 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 classOsoiteTaltioija {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 = newBufferedWriter(new

OutputStreamWriter(new FileOutputStream(new File(tdsto))));Frame f =newFrame("Osoitetietojen taltioija" );f.setBackground(Color.green);f.setSize(500,500);f.setLayout(new GridLayout(6,2));Label l1 =newLabel("Nimi" , Label.RIGHT);Label l2 =newLabel("Katuosoite" , Label.RIGHT);Label l3 =newLabel("Postinumero" , Label.RIGHT);Label l4 =newLabel("Paikkakunta" , Label.RIGHT);t1 = new TextField(30); t2 =new TextField(30);t3 = new TextField(5); t4 =newTextField(20);statusLeima =newLabel("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 =newButton("Talleta" );Button b2 =newButton("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 classTyhjennysKuuntelijaimplementsActionListener {public void actionPerformed(ActionEvent e) {

t1.setText("" ); t2.setText("" ); t3.setText("" ); t4.setText("" );statusLeima.setText("Tyhjennetty" );

}} // TyhjennysKuuntelija

jatkuu . . .

Page 30: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

30 LUKU 3. AWT:N PERUSKOMPONENTTEJA

Esimerkki 3.4. OsoiteTaltioija-sovellus (jatkoa).

jatkuu . . .static classTalletusKuuntelijaimplementsActionListener {

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 classLopetusKuuntelijaextendsWindowAdapter {public void windowClosing(WindowEvent e) {

// Ikkunaa ollaan sulkemassa.try { fout.close(); }catch (IOException e1) { }e.getWindow().dispose(); System.exit(0);

}} // LopetusKuuntelija

} // OsoiteTaltioija

3.6.2 Luokka TextArea

Tämän kohdan lopuksi käsitellään vielä luokkaaTextArea, jonka avulla esitetään monirivisiä teks-tikomponentteja. Tähän luokkaan eiTextField:n tapaa liity mahdollisuutta käyttää kaiutinmerkkejäpeittämään kirjoitettavat merkit. Myöskään enter:n painaminen eiTextField:n tapaan laukaise eri-tyistä tapahtumaa. Taulukossa 3.7 olevien metodien luettelo (ei täydellinen) ei ole kovin yllättävä— TextArea perii suuren osan keskeisestä toiminnallisuudestaanTextComponent:lta ja epäsuorastiComponent:lta.

Lopuksi esimerkissä 3.5 havainnollistetaan erilaisten tekstikomponenttien toimintaa. Kehykseenluodaan kaksi nappulaa, kaksi yksirivistä tekstikomponenttia ja tekstialue keskelle. Alla olevaan teks-tikenttään sovelletaan kirjoitetun tekstin normaalin kaiuttumisen korvaamista merkillä ’*’. Nappuloi-den tehtävänä on kopioida “salatun” kentän sisältö joko tekstialueen alkuun tai loppuun. Ylinnä olevatekstikenttä on sellainen, että sen sisältöä ei voi editoida. Jos tekstialueelta on maalattu hiirellä jokinosa, niin nappuloiden painamisen yhteydessä tämä valittu teksti kopioidaan ylhäällä olevan tekstiken-tän uudeksi sisällöksi. Ks. kuva 3.5.

Huomaa, miten tapahtumankäsittelijän luontioperaatiolle on mahdollista antaa parametri, jonkaperusteella luodaan toiminnaltaan hieman erilaisia tapahtumankäsittelijöitä: parametrin arvo määrää,minne tapahtumankäsittelijä kopioi “salatun” tekstikentän sisällön.

Page 31: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

3.6. TEKSTIKOMPONENTITTEXTCOMPONENT JA TEXTFIELD SEKÄ TEXTAREA 31

TextArea()Konstruktori, joka alustaa sisällön arvoksi tyhjän tekstin.

TextArea(String t )Konstruktori, joka alustaa sisällöksit :n.

TextArea(int r , int c )Konstruktori. Tyhjä tekstisisältö. Rivejär ja sarakkeitac .

TextArea(String t , int r , int c )Konstruktori. Tekstisisältöt , rivejä r ja sarakkeitac .

TextArea(String t , int r , int c , int s )Konstruktori. Kuten edellinen, muttas 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 tekstinstr positioonpos (lisää, ei korvaa).

void append(String str )Lisää tekstinstr loppuun.

int getRows()Palauttaa rivien lukumäärän.

void setRows(int rows )Asettaa rivien lukumääränrows :ksi.

int getColumns()Palauttaa sarakkeiden koon.

void setColumns(int cols )Asettaa sarakkeiden kooncols :ksi.

int getScrollbarVisibility()Palauttaa tiedon liu’uttimien näkyvyydestä.

Taulukko 3.7: LuokanTextArea konstruktoreita ja metodeja.

Kuva 3.5: TextTesti-sovellus, joka havainnollistaa erilaisia tekstikenttiä.

Page 32: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

32 LUKU 3. AWT:N PERUSKOMPONENTTEJA

Esimerkki 3.5 TextTesti-sovellus, joka demonstroi erilaisia tekstikenttiä.

import java.awt.∗;import java.awt.event.∗;

public classTextTestiextendsFrame {private TextField valittu;private TextArea alue;private TextField salattu;

public static void main(String[] args) {newTextTesti();

} // main

public TextTesti() {super("Testailua tekstikomponenteilla" );setBackground(Color.gray);setLayout(new BorderLayout());Button b1 =newButton("Liitä alkuun" );Button b2 =newButton("Liitä loppuun" );add(b1, BorderLayout.EAST);add(b2, BorderLayout.WEST);valittu = newTextField(30);valittu.setEditable(false); // ei saa editoidaalue =newTextArea("tekstiä" , 4, 30);salattu =newTextField(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

classTekstinSiirtajaimplementsActionListener {private booleanalkuunko;TekstinSiirtaja(booleanb) { alkuunko = b; }

public void actionPerformed(ActionEvent e) {String t = salattu.getText();valittu.setText(alue.getSelectedText());if (alkuunko) alue.insert(t,0);elsealue.append(t);

}} //TekstinSiirtaja

} // TextTesti

Page 33: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

Luku 4

Tapahtumat ja tapahtumankäsittely

Tapahtumat(events)ovat Javassa olioita. Niitä ei kuitenkaan tyypillisesti ohjelmassa luoda itse,vaanGUI-ohjelman suorituksen aikana käyttäjän aiheuttamat toimenpiteet, kuten hiirellä klikkaus, näppäi-men painallus tai esimerkiksi hiiren siirtäminen, aiheuttavat tapahtumaolion luonnin käyttöympäristöntoimesta. Tällainen tapahtumaolio välitetään käyttöympäristön ja Javan suoritusaikaisen koneiston toi-mestatapahtumankäsittelijälle(tai useille sellaisille; event handler).Tapahtumankäsittelijä on myösolio. 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 voiliittyä useanlaisiatapahtumankäsittelijöitä. Lisäksi, kunkintyyppisiä tapahtumankäsittelijöitä voi ollauseita— niitä vastaava tapahtumalaukaisee(fire) niidenkaikkien suorittamisen. On vieläpä niin, että yksittäinenkäyttäjän toimenpide voi laukaista useantyyppisiä tapahtumankäsittelijöitä.

AWT:n tapahtumiin liittyvät luokat ovat pääasiallisesti paketissajava.awt.event . Tapahtu-mia esittävien luokkien nimet ovat muotoaXXXEvent, esim.MouseEvent ja ActionEvent. Vastaa-vasti tapahtumankäsittelijöiden muodon kuvaavien rajapintojen nimi on muotoaXXXListener, esim.MouseListener ja ActionListener. Jos rajapinnassa määritellään useita metodeja, AWT:ssä on mää-ritelty ns.adapteriluokka(adapter class), jonka nimi onXXXAdapter, esim.MouseAdapter. Adap-teriluokat toteuttavat kaikki vastaavan rajapinnan metodit — kunkin metodin runko on tyhjä. Adap-teriluokkien tarkoitus on helpottaa tapahtumankäsittelijöiden tekemistä, sillä tyypillisesti rajapinnanmetodeista 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äsi-tellää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. Lopuksiluvussa 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ä ta-pahtumien tuottamis- ja kuljetusmekanismista, (2) komponentteihin liitetystä käsittelymekanismista,(3) tapahtumista ja (4) komponentteihin liitettävistä yksittäisistä tapahtumankäsittelijöistä. Versiossa1.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 esimerkiksiKeyEvent-tapahtumia, sillä on metodit ’addKeyListener’ ja ’removeKeyListener’ tapahtumankäsittelyolioidenkiinnittämiseksi komponenttiin ja vastaavasti poistamiseksi. Yleisesti, jos komponentti tukee tapahtu-

33

Page 34: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

34 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY

maaXXXEvent, 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 kohdistu-via tapahtumia reagoiden niihin tarvittaessa. Reagointi tarkoittaa jonkin tietyn tapahtumankäsittelijänmetodin suorittamista. Käytännössä tapahtumankäsittelijät ovat passiivisia olioita, joihin liitetty toi-minnallisuus suoritetaan yleisen tapahtumankuljetus ja -käsittelymekanismin toimesta.

Tapahtumankäsittelijä on olio, joka on luotu itse tehdystäluokasta. Esimerkiksi, rajapinnastaAc-tionListener voidaan perimällä muodostaa luokka NappulaKuuntelija, kuten esimerkissä 3.2. Perin-nän yhteydessä tapahtumankäsittelijän tarpeellisille metodeille annetaan toteutus tai perittäessä adap-teriluokasta tarpeelliset metodit määritellään uudelleen.

Kun tapahtuma toimitetaan komponentille, komponentteihin liitetty mekanismi osaa kutsua kun-kin tapahtuman yhteydessä sen käsittelystä vastaavaa metodia. EsimerkiksiActionEvent-tapahtumanyhteydessä tämä kutsuttava metodi onActionListener-tyyppisen käsittelijän metodi ’actionPerfor-med(ActionEvent)’, joka on siis itse tehdyssä tapahtumankäsittelijässä määritelty perinnän yhteydes-sä. Kunkin tapahtuman yhteydessä kutsutaan vain yhtä metodia ja sen nimi vaihtelee tapahtumasta (jatapahtumankäsittelijästä) riippuen. Ks. taulukko 4.1.

Tapahtumat ovat olioita, jotka käyttäjän toimenpiteen (“ärsykkeen”) seurauksena syntyvät taustal-la olevan ikkunointijärjestelmän toimesta. Nämä tapahtumat välittyvät JVM:n tapahtumien kuljetus-mekanismille, joka toimittaa tapahtumat sille komponentille, johon tapahtuma kohdistui. Tätä kompo-nenttia kutsutaan tapahtumankohdeolioksi. Tämä toimittaminen tapahtuu viimekädessä järjestelmäntoimesta kutsumalla kyseisen GUI-komponettiolion metodia ’dispatchEvent(AWTEvent)’. Kyseinenmetodi ottaa tapahtumaolion vastaan, jonka jälkeen komponentti prosessoi tapahtuman kutsumallakyseisen olion metodia ’processEvent(AWTEvent)’. Muutaman välivaiheen jälkeen prosessointide-legoituuitse tapahtumankäsittelijöiden kutsumiseksi.

Tapahtumia varten on useita luokkia ja periaatteessa voitaisiin itsekin määritellä uusia tapahtu-maluokkia perimällä, mutta tähän ei yleensä ole mitään tarvetta. Vaikka tapahtumaolioita voidaankinluoda helposti itse, sitäkään ei yleensä tehdä — perustilanne on yleensä täysin riittävä: ikkunointiym-pä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ä tapahtuman-käsittelijöiden tarkastelemiseen on paikallaan muistuttaa, että tapahtumia ei prosessoi se säie, jonkaavulla käyttäjä luo käyttöliittymän, vaan luonnin seurauksena syntyy erityinen säie, joka ottaa tapah-tumia jonosta ja välittää niitä komponenteille kutsuen viime kädessä tapahtumankäsittelijöitä. Huo-maa, 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ätluokaltaEvent. JDK 1.1:n myötä koko ta-pahtumankäsittelyn toimintaa muutettiin paljon ja sen seurauksena JDK 1.1:n jälkeen AWT:n tapah-tumat ovat perineet luokastaAWTEvent, jonka yliluokkana toimii luokkaEventObject. Kuvassa 4.1on esitetty AWT:n 18 luokkaa/rajapintaa, jotka mallintavat erilaisia tapahtumia. Muutamien kohdallayksi luokka mallintaa yhdenlaista tapahtumaa, mutta monetmallintavat useaa samantapaista tapahtu-maa (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) jasemantti-

Page 35: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

4.2. TAPAHTUMAT 35

EventObject

AWTEvent

AdjustmentEvent ItemEvent

HierarchyEvent InputMethodEventInvocationEvent

ComponentEvent

FocusEvent InputEvent PaintEvent

KeyEvent

WindowEventContainerEvent

ActionEvent

MouseEvent

TextEvent

MouseWheelEvent

Kuva 4.1: AWT:n tapahtumien hierarkia.

siin tapahtumiin(semantic event). Matalan tason tapahtumia edustavat mm. luokkien KeyEvent jaMouseEvent oliot. Kaikkien matalan tason tapahtumien yliluokkana toimii luokka ComponentE-vent. Muut luokanAWTEvent aliluokat kuvaavat semanttisia tapahtumia. Terminologinen ero tuleesiitä, että matalan tason tapahtumien merkitys — siis mitä on tapahtunut — on kontektista riippu-maton. Semanttisten tapahtumien merkitys puolestaan riippuu siitä, minkä komponentin yhteydes-sä tapahtuma oikein tapahtuu. Huomaa, että “se mitä on tapahtunut” ei määrittele, miten ohjelmantai yksittäisen komponentin tulisi reagoida kyseiseen tapahtumaan. Tapahtumankäsittelijä määritteleemerkityksen sille, mitä tapahtuma komponentin yhteydessäoikein tarkoittaa.

Eräs yleinen ero matalan tason ja semanttisten tapahtumienvälillä on, että käyttäjän aiheuttamattoimenpiteet ovat ensisijaisesti matalan tason tapahtumia — matalan tason tapahtuma voi samallaaiheuttaa jonkin semanttisen tapahtuman. Huomaa, että josmatalan tason tapahtuma laukaiseen jon-kin semanttisen tapahtuman (tai mahdollisesti useita), sekä matalan tason että semanttiset tapahtumatkulkevat koko tapahtumankäsittelykoneiston läpi. Toinenero on, että tietynlaisen matalan tason ta-pahtuman voikuluttaa, ennen kuin se ehtii aiheuttaa semanttisen tapahtuman. Voisi kuvitella, ettäkuluttaminen tarkoittaa matalan tason tapahtuman tuhoamista, mutta sitä se ei tarkoita — kysymyson vain mahdollisuudesta merkitä tapahtuma kulutetuksi luokastaInputEvent perittävällä metodilla’void consume()’. Kuvan 4.1 perusteella ainoastaan luokkienKeyEvent ja MouseEvent mukaiset ta-pahtumat voidaan kuluttaa1. Kulutettu tapahtuma kulkee kuluttamattomien tapaan kokotapahtuman-käsittelymekanismin läpi — komponenteissa olevien prosessointimetodien pitää ennen tapahtumansoveltamista käsittelijöihin tarkistaa, onko tapahtuma jo kulutettu.

1Itse asiassa metodi ’consume()’ määritellään jo luokassaAWTEvent, mutta vastaInputEvent toteuttaa sen niin, ettätapahtuma voidaan merkitä kulutetuksi.

Page 36: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

36 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY

Jos matalan tason tapahtuma laukaiseen semanttisen tapahtuman, herää kysymys, missä järjes-tyksessä tapahtumat kulkevat käsittelykoneiston läpi? Vastaus kuuluu, että matalan tason tapahtumakäsitellään ensin. Toisaalta, yksittäinen tapahtuma laukaisee kaikki kohdeolioon liitetyt kyseisen tyyp-piset tapahtumankäsittelijät, mutta Java jättää määrittelemättömäksi sen, missä järjestyksessä kyseisettapahtumankäsittelijät suoritetaan!

Kuvan 4.1 perusteella matalan tason tapahtumia varten AWT:ssä on 9 luokkaa. NäistäInputE-vent on abstrakti. Tapahtumaluokat esittävät useita ko. tyypinmukaisia tapahtumia. Kutakin erilaistatapahtumaa kohti kutsutaan tiettyä tapahtumankäsittelijän metodia. Taulukossa 4.1 luetellaan, mitämetodeja tapahtumien kohdalla kutsutaan. Seuraavassa lyhyt selostus kustakin matalan tason tapahtu-maluokasta.

ComponentEvent. Tällä luokalla on kaksi rooli: se toimii matalan tason tapahtumien yliluokkana,mutta samalla se edustaa yleisiä komponenttitapahtumia. Näitä tapahtumia on neljä: komponen-tin siirto, koon muuttaminen, komponentti piiloon ja esiin. Voisi ajatella, että AWT haluaa näinkertoa, milloin tapahtumankäsittelijän pitää tehdä kyseinen muutos. Tästä ei kuitenkaan ole ky-symys. Nämä poikkeukset ovat vain “informatiivisia”: AWT hoitaa kyseiset komponentteihinkohdistuvat muutokset. Sovelluslogiikalle annetaan vainmahdollisuus reagoida myös tapahtu-maan.

FocusEvent. Tähän luokkaan liittyy kaksi erilaista tapahtumaa: komponetti saa tai menettäänäp-päimistöfokuksen. Fokus tarkoittaa sitä, että käyttäjän kirjoittamat merkit kohdistuvat kyseisellekomponentille. Kaikki komponentit eivät voi saada fokusta. Esimerkiksi leimoihin ei kohdistunä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, avautunut1. kertaa näkyväksi, muutettu ikoniksi, ikoni avattu, ikkuna tuli aktiiviseksi (näppäimistövirtakohdistuu 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 pe-rä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 jamerkki tuotettu. Tämän lisäksi luokalla on suuri määrä nimettyjä julkisia vakioita, jotka esittäväteri 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öönkomponentti lisää tai pois. Nämä tapahtumat ovat vain informatiivisia: AWT hoitaa komponen-tin käsittelyyn liittyvän “tekemisen”.

PaintEvent. Liittyy komponentin piirtämiseen uudelleen. Tätä ei ole tarkoitettu otettavaksi kiinnikäyttäjän toimesta.

Page 37: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

4.2. TAPAHTUMAT 37

Tapahtuma & mitä tapahtui käsittelijä & kutsuttava metodiMatalan 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.

Page 38: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

38 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY

Semanttisten tapahtumien merkitys tulee esiin niitä tukevien komponenttien yhteydessä. Taulukon4.1 loppuosassa on lueteltu semanttiset tapahtumat ja niiden seurauksena kutsuttavat metodit.

Tapahtumia esittävillä luokilla on muutamia metodeja konstruktorien lisäksi — näitä metodejaon kuvattu taulukossa 4.2 muutamien tapahtumien osalta. Lisäksi tulevat luokistaEventObject jaAWTEvent 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 suhteessakomponenttiin.⋆ 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.

Page 39: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

4.3. TAPAHTUMANKÄSITTELIJÄT 39

LuokkaEventObject toteuttaa rajapinnanSerializable, joten kaikki AWT:n tapahtumat ovat sar-joittuvia. LuokanEventObject konstruktori ottaaObject-tyyppisen kohdeolion2. Vastaavasti luokallaon havainnointimetodi ’Object getSource()’ kohdeolion havainnoimiseksi — tämä havainnointimeto-di siis periytyy kaikille AWT:n tapahtumille. Taulukkoon 4.3 on koottu luokanAWTEvent keskeisetmetodit — niitä ei ole montaa.

AWTEvent(Event e)Konstruktori, joka saa syötteekseen JDK1.0-tyylisen tapahtumane.

AWTEvent(Object s , int id )Konstruktori, jonka kohteena on olios ; tapahtuman tyyppi onid (kunkin tapahtumaluokankohdalla määritellään lukuväli, jonka arvot edustavat ko.tyypin mukaisia tapahtumia (ilmeisestiyksi 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. VainInputEvent-tapahtumat voi kuluttaa.

protected boolean isConsumed()Havannoija: onko tapahtuma merkitty kulutetuksi?

Taulukko 4.3: LuokanAWTEvent konstruktoreita ja metodeja.

Taulukon 4.3 metodeista voi päätellä, että tapahtumaolioita voi luoda itse ja niitä voi myös sovel-taa komponentteihin saaden tapahtumat laukaisemaan tapahtumankäsittelijöitä. Tällainen menettelyei kuitenkaan vastaa efekteiltään natiivia tapahtuman syntymään ja tapahtuman tulemista osaksi ma-talantason tapahtumien käsittelyjonoa.

Testaus- ja demonstraatiotarkoituksissa on hyvä voida luoda ohjelmallisesti tapahtumia, jotkakäyttäytyvät kuten graafisen käyttöliittymän käyttäjän aiheuttamat natiivit tapahtumat. Tätä vartenAWT:ssa on luokkaRobot, jolla voi luoda natiiveja hiiri- ja näppäintapahtumia. Lisäksi luokallaRo-bot voi ottaa kuvaruutukaappauksia ja saada aikaan viiveitä luotujen tapahtumien välille. Kyseinenluokka on ensisijaisesti tarkoitettu käyttöliittymien regressiotestausta varten – sitä ei käsitellä tässäkohtaa oppimateriaalia vaan vasta luvussa 12.

4.3 Tapahtumankäsittelijät

Graafisten käyttöliittymien pääasiallinen toiminnan ohjauskeino ovat tapahtumankäsittelijät. Kompo-nenteista koostuvan käyttöliittymän luonnin jälkeen sovellus usein3 siirtyy tilaan, jossa sovelluksentoimintaa ohjataan vain komponentteihin käyttöliittymänkautta (hiiri, näppäimistö) kohdistettavillatapahtumilla, jotka puolestaan laukaisevat niihin liitettyjä tapahtumankäsittelijöitä. Koska tapahtu-mankäsittelijät ovat niin keskeisessä roolissa tällaisessa ohjelmoinnissa, käytetään tämänlaisesta oh-jelmoinnista nimitystätapahtumaohjattu ohjelmointi(event-driven programming). Tavallisista peräk-käisistä ohjelmista poiketen huomio on “pienten” toimintojen, tapahtumankäsittelijäolioiden, ohjel-moinnissa. Mitään yhtä ratkaistavaa ongelmaa ei ole, vaan isommissa sovelluksissa pitää kirjoittaa

2Taustalla on siis ajatus, että tapahtumat voivat liittyä muihinkin olioihin kuin GUI-komponentteihin.3Uusien säikeiden käynnistäminen, esim. animointia varten, muodostaa toisen keinon ohjata sovelluksen toimintaa käyt-

töliittymän “maalaamisen” jälkeen.

Page 40: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

40 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY

kymmeniä pienehköjä tapahtumankäsittelijöitä. Tavallaan tapahtumaohjatun ohjelmoinnin vaikeutenavoi pitää koodin “pirstoutumista” yksittäisiin tapahtumankäsittelijöihin. Toisaalta ongelmana on ta-pahtumankäsittelijöidenyhteistoiminnan hallintaja itse ohjelmointivaiheessa keskeinen ongelma onpäättää,miten tapahtumankäsittelijä pystyy vaikuttamaanohjelman muihin graafisiin komponenttei-hin 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 riip-puen) käsittelijän metodin kutsumista. Kun tähän vielä lisätään, että yleensä tapahtumankäsittelijätkirjoitetaan reagoimaan vain yhdenlaiseen tapahtumaan, tarkoittaa se, että tapahtumankäsittelijäoliotovat käytännössäsimuloituja funktioparametreja4. Toisinaan tapahtumankäsittelijä toteutetaan niin,että se saa kaiken toiminnan kannalta tarpeellisen informaation parametriensa ja/tai staattisten luok-kamuuttujien kautta — jolloin tapahtumankäsittelijäolion tietosisältö on tyhjä, sillä ei ole lainkaaninstanssimuuttujia! 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 tapahtu-mankäsittelijä usein kirjoitetaan konkreettisesti metodin, jolla käsittelijä liitetään komponenttiin, pa-rametriosaan.

Seuraavaksi käsitellään eri tapoja toteuttaa tapahtumankäsittelijä perimällä — pohtien samalla ta-pojen etuja ja haittoja. Tapahtumankäsittelijöiden muodon määräävät rajapinnat, joiden metodit esiin-tyivät jo taulukossa 4.1 — kuhunkin käsittelijään liittyy hyvin vähän metodeja. Itse asiassa kaikkientapahtumankäsittelyrajapintojen yliluokkana toimiiEventListener, mutta se on vain “merkkausraja-pinta”, joka ei määrittele yhtään metodia. Kirjoittamisenhelpottamiseksi ovat käytettävissä adapteri-luokat (jokaista sellaista rajapintaa kohti, jolla käsitellään useita samantapaisia tapahtumia). Tämänkohdan lopuksi on vielä paikallaan huomata, että käsittelijöiden kiinnittäminen komponentteihin (japoistaminen) on ajonaikaista — eli tapahtumankäsittely onJavassa hyvindynaamista.

4.3.1 Tapahtumankäsittelijän toteutusvaihtoehdoista

Tapahtumankäsittelijät muodostetaan toteuttamalla rajapinta tai perimällä adapteriluokasta (tai eri-koistamalla jostakin toteutetusta tapahtumankäsittelijäluokasta). Toteuttamiseen on kuitenkin useitahyvin erilaisia vaihtoehtoja:

1. normaali itsenäinen luokka,

2. (staattinen) sisäluokka,

3. anonyymi (staattinen) sisäluokkatai

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älit-tömästi tapahtumankäsittelijäluokan konstruktorin kutsun yhteyteen seuraavasti:

new Yliluokka(. . . ){. . . metodien (ja muuttujien määrityksiä) . . .

}

4Jos Java tukisi funktioparametereja, niin ehkäpä tapahtumankäsittelijät olisivat vain yksittäisiä metodeja.

Page 41: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

4.3. TAPAHTUMANKÄSITTELIJÄT 41

Edellisessä ’Yliluokka’ on esimerkiksiMouseAdapter, josta siis tulee aaltosulkujen sisällä määri-teltävän tapahtumankäsittelijäluokan yliluokka. ’Yliluokka’ voi olla myös rajapinta, esimerkiksiAc-tionListener! Sisäluokalle muodostuu implisiittisesti konstruktori,jolla on ’(. . . )’-osan mukaiset pa-rameterit ja jonka rungossa suoritetaan ’super(. . . )’. Anonyymillä (ali)luokalla ei voi olla eksplisiitti-sesti määriteltyä konstruktoria. Tyypillisesti anonyymin sisäluokan mukaisen tapahtumankäsittelijänmää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 komponent-tiin ottamatta talteen viittausta siihen. Toisaalta anonyymin sisäluokan mukainen olio voidaan tallettaamyös yliluokan mukaiseen muuttujaan kuten esimerkissä 4.1myös tehdään.

Toteuttamistavan valinnan kannalta olennaista on, miten käsittelijä pääsee käsiksi niihin toimin-taympäristönsä olioihin, joihin sen tulisi toiminnallaanvaikuttaa. Tyyppillisesti käsittelijän pitäisi vai-kuttaa 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änlomakkeen tiedot tallennetaan vaikkapa tietokantaan. Tapahtumankäsittelijä on kiinnittynyt nappu-laan — vaikka se saakin selville kohdeolionsa, sen kautta seei pääse käsiksi esimerkiksi lomakkeentekstikenttien sisältöön. Käsittelijän pitää päästä käsiksi niihin, jotta se osaa lukea niiden sisällön (javaikkapa “nollata” sisällön samalla). Lisäksi käsittelijän pitää jotenkin päästä käsiksi myös tietokan-taan 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 tieto-sisältöä;

2. (ulko)luokan staattinen muuttuja, jolloin pitää vain huolehtia siitä, että muuttuja on käsittelijänkäytettävissä;

3. (ulko)luokan instanssimuuttujankautta, jolloin pitää taaskin huolehtia siitä, että käsittelijä pää-see käsiksi muuttujaan;

4. nimetyn vakionkautta — viite olioon voidaan tallettaa nimettyyn vakioon,jolloin ongelmanaon taas vakion näkyvyys; ja

5. kohdeolionkautta 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 seedellyttää omien GUI-komponenttienmuodostamista. Ensimmäinen keino on suositeltavin, koskasilloin käsittelijän riippuvuus ympäris-töstään on minimaalinen: kaikki tarpeellinen voidaan välittää konstruktorin kautta. Tuolloin käsitte-lijä muodostaa selkeän itsenäisen kokonaisuuden ja muutenkin tiedon kapseloinnin hyviä periaatteitaon mahdollista noudattaa. Tavat 2 – 4 ovat itse asiassa myös varsin tavallisia: sisäluokkien, varsinkinanonyymien sisäluokkien, tapauksessa niitä usein käytetään. Anonyymien sisäluokkien kohdalla käyt-tö on puolusteltua, mutta tavallisten sisäluokkien kohdalla voitaisiin hyvin välttää luokkamuuttujienkäyttö. Instanssimuuttujien käyttö on tavallisten sisäluokkien kohdalla luonnollista.

4.3.2 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 suosi-teltavinta on tietysti tehdä siitä tavallinen itsenäinen luokka (ks. esimerkki 4.4 sivulla 47). Tapahtu-mankäsittelijät ovat valitettavasti usein niin sidoksissa sovelluksen logiikkaan, että niitä ei voi pitää

Page 42: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

42 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY

sellaisenaan yleiskäyttöisinä5 – jotakin sovelluksen (isohkoa) osaa toisaalta voidaan hyvinkin uudel-leenkäyttää (erityisesti sovellusperheen tapauksessa).

Jos on tehty uusi GUI-komponentti erikoistamalla jotakin olemassaolevaa komponenttia ja senyhteyteen liitetään kiinteästi siihen liittyvä tapahtumankäsittelijä, niin hyvä ratkaisu olisi käyttää si-säluokkia. Tuolloin suuri osa tiedosta voi tulla sisäluokan mukaisen käsittelijäolion käyttöön suoraansiihen liittyvän ulkoluokan mukaisen olion tietosisällöstä (johon käsittelijä automaattisesti pääsee si-säluokan oliona käsiksi).

Staattiseen sisäluokkaratkaisuun pitää turvautua, jos esimerkiksi main-metodissa luodaanFrame-olio ja rakennetaan sovellus liittämällä GUI-komponentteja siihen. Tuolloin ei ole olemassa ulkoluo-kan mukaista oliota ja tiedonvälityskeinoina ovat lähinnäluokkamuuttujat ja konstruktorin parame-terit. 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 onkuitenkin suositeltavaa, jos käsittelijän toiminta on niin tiukasti sovellukseen liittyvää, ettei sitä voipitää yleiskäyttöisenä. Turvautumalla yksityiseen sisäluokkaan, pysyy sovelluksen julkisten luokkienmäärä paremmin hallittavana.

Anonyymit sisäluokat on esitelty Java-kieleen lähinnä tapahtumankäsittelijöiden toteuttamistavarten. Tyypillisesti käsittelijöitä on paljon ja yksi käsittelijä tekee suhteellisen yksinkertaisen asiantoteuttaen vain yhden rajapinnan metodeista (usein yliluokkana käytetään adapteria). Tähän tarkoi-tukseen anonyymit sisäluokat tarjoavatkompaktinmutta samalla valitettavan sekavan tavan koodatatapahtumankäsittelijä. Anonyymeillä sisäluokilla tulisi toteuttaa vain “lyhyitä” tapahtumankäsitteli-jöitä — usein tällaiset ovat “kertakäyttöisiä”: ne halutaan vain liittää GUI-komponenttiin ottamattatalteen viitettä käsittelijään. Anonyymin sisäluokan mukaiselle oliolle välitetään tietoa lähinnä vainkohdeolion, instanssi- sekä luokkamuuttujien ja nimettyjen vakioiden kautta. Huomaa, että anonyymisisäluokka on implisiittisesti staattinen, jos se luodaanstaattisen metodin sisällä!

Lopuksi, usein tapahtumankäsittelijöitä näkee liitettävän osaksi luokkaa, jonka rooli on jokin aivanmuu. Esimerkiksi muodostettaessa omaa applettia perimällä luokastaApplet, voidaan samalla toteut-taa usean tapahtumankäsittelijän rajapinnan metodit ja sitä kautta luotava appletti-olio toimii samallamyös tapahtumankäsittelijänä. Tällaista käytäntöä ei voipitää suositeltavana. Toteuttamalla käsittelijäsisäluokkana saadaan aikaan aivan sama toiminnallisuus, mutta käsittelijä muodostaa huomattavastiselkeämmän kokonaisuuden.

4.4 Tapahtumankäsittelyn toiminta

Miten tapahtumankäsittely on Javassa oikein toteutettu? Tähän kysymykseen ei ole kovin helppo saa-da vastausta. Vastausta kaipaavat esim. seuraavat kysymykset:

• missä tapahtumaolio oikein luodaan?

• mikä on AWT:n GUI-komponenttien ja niiden vastionolioidensuhde 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?

5Tavallaan tämä on luonnollinen seuraus siitä, että GUI-sovelluksen toiminta toteutetaan juuri tapahtumankäsittelijöillä.

Page 43: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

4.5. ESIMERKKEJÄ 43

Kun käyttäjä kohdistaa esim. hiirellä jonkin toimenpiteenjohonkin komponenttiin, taustalla olevaikkunointijärjestelmä tunnistaa, tapahtuman ja sen, mihin vastinolioon se kohdistuu. Ikkunointijär-jestelmän toimesta vastinolio saa tiedon tapahtumasta — AWT:n tapahtumaolio syntyy vastionoliontoimesta. Vastionolio lähettää tapahtumaolion AWT:n GUI-komponentille. Olisi mahdollista ajatel-la, että vastionoliot hoitavat ainoastaan matalan tason tapahtumia ja semanttisten tapahtumien tuot-taminen (ja reagointi) tapahtuisi GUI-komponttien toimesta: tuottaminen olisi helppo toteuttaa liit-tämällä komponentteihin vain tapahtumankäsittelijä, joka kuuntelee matalan tason tapahtumia. Näinei ole kuitenkaan toimittu, vaan AWT:n semanttiset tapahtumat tuotetaan ilmeisesti myös vastinoliontoimesta — tuolloin vastionolion pitää ennen semanttisen tapahtuman tuottamista tutkia kuluttaakojokin GUI-komponentin tapahtumankäsittelijä tapahtuman. Itse asiassa vastinolion pitää soveltaa ta-pahtumaa myös itseensä, mutta ennen sen tekemistä, se antaaGUI-komponenteille mahdollisuudenkuluttaa 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 GUI-komponentille, vaan se laitetaan JVM:n ylläpitämäänEventQueue-tyyppiseen jonoon. Sieltä erilli-nenEventDispatchThread-tyyppinen säie hakee tapahtumia ja lähettää ne edelleen GUI-komponen-teille. Jonoon voi tulla hyvin paljon tapahtumia — nopeammin kuin säie ehtii soveltaa niitä GUI-kom-ponentteihin. Tästä johtuen jonoon liittyy mekanismi, jonka seurauksena tapahtumia voidaan “yhdis-tää”. 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 nappu-la, jonka avulla niiden sisältö lisätään tekstikentän loppuun. Tekstikenttään voi itse yrittää kirjoittaamerkkejä, 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 mata-lan tasonKeyEvent-tapahtumiin kuluttaen kaikkien muiden merkkien painalluksen paitsi numeroi-den. Huomaa, miten tapahtumankäsittelijä on toteutettu staattisena anonyyminä sisäluokkana, jonkatoiminta nojautuu vain tapahtumaolioon. Kumpaankin nappulaan liitetään esimerkissä sama tapahtu-mankä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äli-tyksellä. Huomaa, että vaikka tekstikenttään kohdistuva ’a’:n lisääminen kulutetaankin tapahtuman-käsittelijän kautta, nappulan ’a’ kautta merkin ’a’ lisääminen kuitenkin onnistuu6. (Esimerkin 4.1toiminta kaipaisi vielä hiontaa: tekstikenttään voi vainlisätä merkkejä — tuhoamismerkkikin suoda-tetaan pois . . . )

6Ei-numeroiden suodatus pitäisi laittaa “alemmas”. Näin onmahdollista tehdä Swing:n tapauksessa, jolloin tekstikentänsisältö muodostuu erillisestä mallioliosta.

Page 44: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classNumeroKentta {

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 =newFrame("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öinenja sen seurauksena sovellus lopetetaan. Koodi on esimerkissä 4.4.

Page 45: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

4.5. ESIMERKKEJÄ 45

Kuvassa 4.3 havainnollistetaan esimerkkiä 4.2, jossa luodaan itse tapahtuma ja välitetään se GUI-komponentille, keskellä olevalle “Lisää”-nappulalle. Huomaa, miten tapahtuma luodaan ja välitetään’dispatchEvent’-metodilla komponentille. Tapahtumien ohjelmallinen luonti ja soveltaminen kompo-nentteihin mahdollistasovellusten toiminnallisuuden demonstroinnin– laajamittaista demonstrointiavarten kannattaa käyttää luokkaaRobot, jota tarkastellaan tarkemmin testauksen yhdeydessä luvus-sa 12. Esimerkissä 4.2 keskellä olevan nappulan klikkaus aiheuttaa ylhäällä tekstikentässä esitettävänlaskurin arvon kasvattavamisen yhdellä. Alimmaksi sijoitettua leimaa voi myös klikata, jolloin siihenliitetty ’HiiriKuuntelija’-tyyppinen käsittelijä laukeaa välittäen tapahtuman keskellä olevalle nappu-lalle, joka puolestaan kasvattaa laskurin arvoa yhdellä. Huomaa, miten klikkaus otetaan kiinni erilai-silla 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 classItseLaukaisu {

private static TextField t =newTextField(20);private static int laskuri = 0;private static Button nappula =new Button("Lisää" );

public static void main(String[] args) {Frame f =newFrame("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 classKlikkausKuuntelijaimplementsActionListener {public void actionPerformed(ActionEvent e)

{ laskuri++; t.setText("Laskuri = " + laskuri); }} // KlikkausKuuntelijastatic classHiiriKuuntelija extendsMouseAdapter {

public void mouseClicked(MouseEvent e) {nappula.dispatchEvent(new

ActionEvent(nappula, ActionEvent.ACTION_LAST,"" ));}

} // HiiriKuuntelija} // ItseLaukaisu

Page 46: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classHiiriTesti {

private static TextField t =newTextField("Klikkaa!" );public static void main(String[] args) {

Frame f =newFrame("HiiriTesti" );t.setEditable(false); f.add(t);t.addMouseListener(new HiiriKuuntelija());f.addWindowListener(new IkkunanSulkija());f.pack(); f.setVisible(true);

} // mainstatic classHiiriKuuntelija extendsMouseAdapter {

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 hiirenpainalluksen yhteydessä voidaan tutkiaonko esim. Shift-näppäin alhaalla. Samalla esimerkki havainnollistaa hiiren kursorikuvan vaihtamis-mahdollisuutta. Kehykseen sijoitetaan yksi tekstikenttä, johon liitetään tapahtumankäsittelijä. Viemäl-lä hiiri tekstikentän päälle, ja painamalla näppäin pohjaan, syntyy tapahtuma, jonka seurauksena kut-sutaan käsittelijän metodia ’mousePressed’. Kutsu saa aikaan kursorikuvan muuttumisen. Kun hiiritaas päästetään ylös, syntyy tapahtuma, jonka seurauksenametodin ’mouseReleased’ kutsu muuttaa

Page 47: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

4.5. ESIMERKKEJÄ 47

kursorikuvan takaisin normaaliksi. Kokonaisuudesta syntyy vielä klikkaustapahtuma, joka aiheuttaakursorikuvan muuttumisen ’käden kuvauksi’, jos klikkauksen aikana on Shift ollut painettuna poh-jaan. Kehykseen liittyy vielä tapahtumankäsittelijä, joka ikkunan sulkemisen seurauksena lopettaakoko sovelluksen.

Esimerkki 4.4 Yleiskäyttöinen käsittelijä sovelluksen lopettamiseksi.

import java.awt.event.∗;public classIkkunanSulkijaextendsWindowAdapter {

public void windowClosing(WindowEvent e){ e.getWindow().dispose(); System.exit(0); }

} // IkkunanSulkija

Page 48: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

48 LUKU 4. TAPAHTUMAT JA TAPAHTUMANKÄSITTELY

Page 49: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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

LuokkaContainer määrittelee säiliöiden ominaisuudet toimien kaikkien säiliöluokkien yliluokkana.Container perii luokastaComponent, joten säiliöt ovat samalla myös GUI-komponentteja. Säiliönperusominaisuus on, että se koostuu kokoelmasta komponentteja. Komponentit muodostavat listanja näkyvyysmielessä uudet komponentit lisätään edellisten alle. Komponenttien lisäämiseksi säiliöönluokkaContainer määrittelee useita ’add’-metodeja. Komponenttien lisäksi säiliöön liittyy sijoittelu-manageri(layout manager), josta lisää luvussa 5.4.

LuokkaContainer ei ole abstrakti JDK 1.2:sta eteenpäin (JDK 1.1: abstrakti). Sillä on konstruk-tori, muttaContainer-komponentit ovatkeveitä– 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 muillesäiliöluokille.

Container() — Konstruktori, joka luo tyhjän komponenttisäiliön.Component add(Component comp) — Lisää komponentincomp (loppuun). Palauttaacomp:n.Component add(Component comp, int ind ) — Lisää komponentincomp kohtaanind (syrjäyt-

tämättä). Josind = -1 =⇒ loppuun. Palauttaacomp:n.void add(Component comp, Object c ) — Lisää komponentincomp (loppuun) rajoituksellac . Ra-

joitus c ohjaa sijoittelumanagerin toimintaa. Esim. merkkijono"South" . Informoi sijoittelu-manageria.

void add(Component comp, Object c , int ind ) — Kuten edellinen, mutta lisäys kohtaanind .Component add(String name, Component comp) — Lisää komponentincomp, johon liittyy ni-

mi (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äkyvil-

lä komponenteilla ja soveltaa rekursiivisesti alisäiliöihin.Component getComponent(int n) — Palauttaan. 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

Page 50: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

50 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA

boolean isAncestorOf(Component c ) — Onkoc osa säiliötä tai jotain sen alisäiliötä?void remove(Component comp) — Poistaa säiliöstä komponentincomp.void remove(int ind ) — Poistaa säiliönind . komponentin.void removeAll() — Poistaa säiliön kaikki komponentit.void addContainerListener(ContainerListener l ) — Lisää säiliötapahtumien kuuntelijanl .void removeContainerListener(ContainerListener l ) — Poistaa säiliökuuntelijanl .LayoutManager getLayout() — Palauttaa säiliöön liitetyn sijoittelumanagerin.void setLayout(LayoutManager mgr) — Asettaa säiliölle sijoittelumanagerinl .void invalidate() — Merkitään säiliö (ja kaikki sen yläpuolisetsäiliöt) sellaiseksi, että niiden kompo-

nenttien sijoittelu pitäisi uusia.void validate() — Tekee säiliön komponenttien sijoittelun (rekursiivisesti) uudelleen.

Taulukko 5.1: LuokanContainer konstruktoreita ja muutamia metodeja.

5.2 GUI-sovellusten perussäiliöFrame ja luokka Window

LuokkaWindow on Container:n välitön aliluokka ja samalla se on GUI-sovellusten yliluokka. Var-sinaiset GUI-sovellukset rakennetaan luokkaaFrame käyttäen jaFrame on Window:n aliluokka.Window on ikkuna, jollaei ole reunoja eikä menupalkkia. Oletuksena se käyttääBorderLayout-sijoittelumanageria.Window on sikäli omituinen luokka, että se on konkreetti, mutta seei ole itse-näinen, vaan kaikkiWindow-oliot vaativat tuekseen omistajaolion, käytännössäFrame- tai Dialog-tyyppisen olion.

VaikkaWindow on komponentti,sitä ei saa laittaa toiseen komponenttisäiliöön— sama koskeealiluokkia Frame ja Dialog. Ainoa komponenttisäiliö, jonka voi sijoittaa toiseen säiliöön onPanel-tyyppinen olio (tai aliluokan mukainen olio, esim.Applet!).

Luokka Window määrittelee ikkunoiden yleisiä ominaisuuksia. Muutamia näistä on koottu tau-lukkoon 5.2.

LuokkaFrame (kehys) toimii GUI-sovellusten “pääluokkana”. Sellaisiaon vähintään yksi AWT-sovelluksessa. Kehysolioilla onreunat (border),otsikkopalkki ja mahdollisuusmenupalkkiin(me-nubar). Oletusarvoisesti kehysolioihin liittyyBorderLayout-sijoittelumanageri. Jos käytettävissä onuseita näyttölaitteita (fyysisiä tai virtuaalisia), on mahdollista määrittää, mihin kehysolio liitetään(Window samoin). Ikkunatapahtumien osalta kaikki 7 tapahtumaa ovat mahdollisia —Window:nkohdalla mahdollisia ovat vain ’windowClosed’, ’windowOpened’. Taulukossa 5.3 on lueteltuFrame-luokan metodeja ja konstruktoreita.

Window(Frame owner ) — Luo ikkunan, jonka omistaja onowner . Näkymätön, turvallisuusmana-geri voi liittää “bannerin” osaksi luotavaa ikkunaa (esim.applettien tapauksessa).

Window(Window owner ) — Kuten edellinen, mutta nyt omistaja onowner .void addWindowListener(WindowListener l ) — Lisää tapahtumankäsittelijänl .void removeWindowListener(WindowListener l ) — Poistaa ikkunatapahtumien käsittelijänl .void dispose() — Tuhoaa ikkunan ja sen komponentteihin (rekursiivisesti) liittyvät vastinoliot. Va-

pauttaa niiden muistiresurssin. Oliot ovat edelleen olemassa; “identtiset” vastinoliot voidaantaas luodashow() :lla (tai oikeammin setVisible(true):lla).

void toFront() — Nostaa ikkunan päällimmäiseksi (graafisen KJ:nikkunoiden joukossa).void toBack() — Laittaa ikkunan “alimmaksi”.void pack() — Mukauttaa ikkunan suositeltavaan kokoon (tietoa komponenteilta ja sijoittelumanage-

rilta). Lisäksi: näkyväksi + validointi.

Page 51: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 (turval-

lisuusmanageri).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: LuokanWindow konstruktoreita ja muutamia metodeja.

Kuva 5.1: Broadcasters-sovellus suorituksessa.

Page 52: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

52 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA

Frame() — Konstruktori. Näkymätön ei otsikkotekstiä.Frame(String title ) — Konstruktori. Näkymätön, otsikkotitle .String getTitle() — Palauttaa otsikkotekstin; ""=⇒ ei otsikkoa.void setTitle(String title ) — Asettaatitle :n otsikoksi.Image getIconImage() — Palauttaa kuvan, jota käytetään ikonisoitaessa ikkuna (X11).void setIconImage(Image image ) — Asetaa ikonisoinnissa käytettävän kuvanimage .MenuBar getMenuBar() — Palauttaa ikkunan menupalkin (null: ei ole).void setMenuBar(MenuBar mb) — Asettaa menupalkiksimb: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 vaiei (+muita).void remove(MenuComponent m) — Poistaa menupalkista komponentinm.static Frame[] getFrames() — Palauttaa kaikki kehykset, joita sovelluksen yhteydessä on luotu.

Appletin tapauksessa vain ne, jotka ovat ko. applettiin liittyviä.

Taulukko 5.3: LuokanFrame konstruktoreita ja metodeja.

Seuraavaksi esimerkissä 5.1 (sivu 53) on GUI-sovellus, jossa voidaan luoda dynaamisesti uusiaikkunoita. Samalla ikkunoihin on liitetty toiminnallisuus, jonka kautta tekstikenttään kirjoitettu tekstivoidaan välittää kaikille muille ikkunoille. Esimerkin tarkoitus on havainnollistaa ikkunoiden käsit-telyä yleensä. Esimerkkiä on havainnollistettu kuvassa 5.1 (sivu 51). Itse sovellus koostuu kahdes-ta luokasta: pääohjelmasta ’Broadcasters’, jonka tehtävänä on vain luoda yksi ’Broadcaster’-olio jatehdä 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 muute-taan 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ä kaksitekstikenttää: ylemmän avulla voi lähettää viestin toisille ikkunoille — lähetetty viesti näkyy alempa-na olevassa (estetyssä) tekstikentässä. Keskellä olevaantekstikenttään on liitetty tapahtumankäsitteli-jä, joka laukeaa ’return’:n painamisen seurauksena ja käy staattisen ’getFrames’-metodin avulla läpisovelluksen kaikki ikkunat, soveltaen jokaiseen ’Broadcaster’-tyyppiseen olioon metodia ’setMessa-ge’ (joka on toteutettu luokan lopussa). Konstruktorin lopussa jokaiseen luotavaan ikkunaan liitetäänvielä tapahtumankäsittelijä, joka reagoi ikkunan sulkemistapahtumiin. Ideana on tuhota suljettavanikkunan vastinolio ja pyrkiä lopettamaan koko sovellus, kun viimeinen ikkuna suljetaan. Ohjelma eikuitenkaan toimi viimeisen ominaisuuden osalta: ’Frame.getFrames().length’ palauttaa sovellukses-sa olevien ikkunoiden lukumäärän — tämä lukumäärä ei vähenevastinolion tuhoamisen seurauksena(JDK 1.3). Tältä osin korjaaminen on tosin helppoa: pidetään vain kirjaa luotujen ikkunoiden ja sul-jettujen lukumäärästä, jolloin voidaan päätellä, milloinollaan sulkemassa viimeistä ikkunaa – kokeilekorjata 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 ovatjo liian laajoja – niistä olisi ollut järkevää tehdä tavallisia sisäluokkia. Huomaa lopuksi, miten toises-sa tapahtumankäsittelijässä käytetään ilmausta Broadcaster.this – kyseinen tapahtumankäsittelijä onolio, pelkkä ’this’ viittaisi tapahtumankäsittelijäolioon.

Page 53: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classBroadcasters {

public static void main(String[] args) {Broadcaster b =new Broadcaster("Br" ,1); b.setVisible(true);

} // main} // Broadcasters

classBroadcasterextendsFrame {private int counter;private TextField message =new TextField(20);private String otsikko;Broadcaster(String title,int c) {

counter = c; otsikko = title; setTitle(title);setLayout(newGridLayout(0,2));Button b1 =new Button("Tee uusi" );Button b2 =new Button("Ikoneiksi muut" );TextField t1 =newTextField(20);message.setEditable(false); add(b1); add(b2);add(newLabel("Lähetä" )); add(t1);add(newLabel("Saatu" )); add(message);b1.addActionListener(newActionListener(){

public void actionPerformed(ActionEvent e) {Broadcaster br =new Broadcaster(otsikko+"." +counter,1);counter++; br.setVisible(true); }});

b2.addActionListener(newActionListener(){public void actionPerformed(ActionEvent e) {

Frame[] ikkunat = Frame.getFrames();for (int i=0; i<ikkunat.length; i++)

if (ikkunat[i] instanceofBroadcaster) {Broadcaster br = (Broadcaster)ikkunat[i];if (br 6= Broadcaster.this) br.setState(Frame.ICONIFIED);

} }});t1.addActionListener(newActionListener(){

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] instanceofBroadcaster) {Broadcaster br = (Broadcaster)ikkunat[i];br.setMessage(otsikko+"says: " +viesti);

} }});addWindowListener(newWindowAdapter(){

public void windowClosing(WindowEvent e){e.getWindow().dispose();if (Frame.getFrames().length == 1) System.exit(0);

}});pack();

} // Broadcastervoid setMessage(String msg) { message.setText(msg); }

} // Broadcaster

Page 54: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

54 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA

5.3 Komponenttien säiliöPanel

LuokkaPanel (paneeli) on yksinkertainen komponenttien säiliö, joka perii suoraan luokaltaContai-ner. Se toimii yliluokkana luokalleApplet. Paneelin tarkoitus on mahdollistaa komponenttien hierark-kinen ryhmittely. Muihin komponenttisäiliöihin verrattuna paneelilla on tärkeä ominaisuus:se on kom-ponentti ja sen saa sijoittaa toiseen säiliöön. Oletusarvoisesti paneelissa on käytössäFlowLayout-sijoittelumanageri.

Luokalla Panel on perusmuotoinen konstruktori ’Panel()’, joka tekee tyhjän komponenttisäiliönvarustettunaFlowLayout-sijoittelumanagerilla. Konstruktorista on ylikuormitettu versio, jossa sijoit-telumanageri annetaan. Muut metodit on peritty luokistaComponent ja Container (tosin joitakin onhieman muutettu perinnän yhteydessä).

5.4 Sijoittelumanagerit

AWT:n osana on (ainakin) 5 sijoittelumanageria. Ne on ryhmitelty rajapintojenLayoutManager jaLayoutManager2 alle, kuten kuvassa 5.2 esitetään. Sijoittelumanagerit ovat seuraavat:

BorderLayout: Määrittelee 5 kohtaa (itä, länsi, etelä, pohjoinen, keskellä), jonne komponentteja voi-daan sijoitella.

CardLayout: Toteuttaa sijoittelumanagerin, johon sijoitetut komponentit ovat kuin päällekkäiset “kor-tit”.

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.

LayoutManager2

GridLayout BorderLayout

FlowLayout

LayoutManager

CardLayout

GridBagLayout

Kuva 5.2: Sijoittelumanageriluokkien luokkahierarkia.

Kuvassa 5.2 esiintyvät rajapinnat määrittelevät sijoittelumanagereille muutamia metodeja, esimer-kiksi ’addLayoutComponent’, ’removeLayoutComponent’, ’layoutContainer’, . . . . Näitä metodeita eiohjelmoijan ole kuitenkaan tarkoitus kutsua, vaan komponentin sijoittaminen säiliöön (vastaavastipoistaminen) tekee automaattisesti tällaisen kutsun. Tärkeä ero luokkienLayoutManager ja Layout-Manager2 välillä on, miten sijoittelumanagerille kerrotaan, mihintai miten komponentti kuuluisi

Page 55: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

5.4. SIJOITTELUMANAGERIT 55

sijoitella. LuokanLayoutManager-tapauksessa tätä informaatiota ei ole (järjestykseen perustuva toi-minta). LuokkaLayoutManager2 yleistää tätä siten, että komponentteihin liittyy sijoittelua ohjaavarajoitus: tämä rajoitus on yksinkertaisimmillaan merkkijono, mutta luokanGridBagLayout rajoituskerrotaan erityisellä oliolla. Ero tulee esiin metodissa ’addLayoutComponent’, joka on muotoa

void addLayoutComponent (Component c , Object constraints )

5.4.1 Säiliö ilman sijoittelumanageria

LuokanContainer metodilla ’setLayout’ asetetaan sijoittelumanageri säiliölle, mutta sen avulla onmyös mahdollista poistaa sijoittelumanageri komponentista. Tämä tapahtuu käyttämällä kutsussa pa-rametrianull.

Kun säiliöllä ei ole sijoittelumanageria, pitää jokaisen komponentin yhteydessä ilmoittaa kom-ponentin sijainti (pikseleinä) ja koko. Komponentit “naulataan” kiinteästi kyseisten määreiden avullasäiliöön. Säiliön koon muuttaminen ei tuolloin vaikuta komponenttien sijoitteluun. Tällaisen tavanhankaluutena on edellisten määreiden tarkka laskeminen kaikille sijoiteltaville komponenteille, muttatoisaalta käyttöliittymän ulkoasusta saadaan varmemmin juuri sellainen kuin halutaan.

5.4.2 Luokka GridLayout

LuokanGridLayout mukainen sijoittelumanageri määrittelee säiliöönx×y-“ristikon”, johon sijoitel-laan komponentteja ylhäältä alas ja vasemmalta oikealle lisäysjärjestyksessä. Sijoittelumanageriantaakaikille ruuduille yhtä paljon tilaa. Konstruktorin yhteydessä voi määrittää rivien ja sarakkaiden luku-määrän.Entä jos komponentteja liikaa?Sijoittelumanageri on joustava —sarakkeiden määrä joustaatarvittaessa. 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ääritteleex 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) javgap (pysty). PoikkeusIllegalArgumentException, jos hgap taivgap laiton.

⋆ Edellä astetuille arvoille on havainnointimetodit: ’getColumns’, ’getRows’, ’getHgap’, ’getV-gap’.

⋆ Samoin modifiointimetodit: ’setColumns’, ’setRows’, ’setHgap’, ’setVgap’.

Taulukko 5.4: LuokanGridLayout konstruktoreita ja metodeja.

5.4.3 Luokka FlowLayout

Ideana on sijoitella komponenttejariveittäin, vasemmalta oikealle, niin monta kuin yhdelle rivillemahtuu. Oletusarvoisestikeskittääkunkin muodostetun rivin. Mahdollista tasata myös jompaankum-paan reunaan (vakiotFlowLayout.CENTER, LEFT, RIGHT). Komponentit saavat esiintyä eri kokoi-sina (preferred size).

Page 56: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

56 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA

FlowLayout() — Konstruktori; sovelletaan oletusarvoisesti gap-arvoa 5 molempiin suuntiin.FlowLayout(int align ) — Edellisen lisäksi rivit orientoidaanalign :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: LuokanFlowLayout konstruktoreita ja metodeja.

5.4.4 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ää edel-lisiä luokanBorderLayout nimettyjä vakioita. Oletusalueena toimiiBorderLayout.CENTER. Kom-ponentit saavat luonnollisen kokonsa (preferred size), mutta säiliön rajoja pitää noudattaa. (NORTHja SOUTH: venyvät vaakasuunnassa; EAST ja WEST: pystysuunnassa; CENTER: voi täyttää vapaantilan.)

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: LuokanBorderLayout konstruktoreita ja metodeja.

5.4.5 Luokka CardLayout

LuokanCardLayout 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 suh-teen AWT:n kuvaus on epämääräisempi. Sen sanotaan määräytyvän säiliön sisäisen järjestyksen mu-kaan — käytännössä järjestys on lisäysjärjestys. Komponentteihin voidaan kuitenkin liittää nimi ja senperusteella voidaan nostaa nimetty “kortti” päällimmäiseksi. Taulukossa 5.7 on luokanCardLayoutkonstruktoreita 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äällim-

mäiseksi.

Taulukko 5.7: LuokanCardLayout konstruktoreita ja metodeja.

Esimerkissä 5.2 käytetäänCardLayout-sijoittelumanageria. Säiliöön sijoitetaan kolme kompo-nenttia, joihin sijoittamisen yhteydessä liitetään nimet(merkkijonoja). Nämä samat nimet laitetaanosaksi alaosaan sijoitettavaa pudotusvalikkoa ja valikkoon liitetään tapahtumankäsittelijä, joka valin-nan perusteella nostaa nimen mukaisen komponentin päällimmäiseksi.

Page 57: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

5.4. SIJOITTELUMANAGERIT 57

Kuva 5.3: SijoittelumanageriCardLayout:n havainnollistusta.

Esimerkki 5.2 CardLayout-sijoittelumanagerin havainnollistus.

import java.awt. ∗;import java.awt.event. ∗;public classCardTest {

private static Panel p =newPanel();

public static void main(String[] args) {Frame f =newFrame("CardLayout kokeilu" );f.setSize(200,200);f.setLayout(new BorderLayout());p.setSize(150,150);p.setLayout(new CardLayout());Button b1 =newButton("Nappula" );Button b2 =newButton("Toinen" );TextField t =newTextField("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

Page 58: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

58 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA

5.5 Liukuvalitsin Scrollbar

Scrollbar on tuttu liu’utin, joka voi ollavaaka- tai pystysuuntainen. Liu’utinta liikutettaessa siihen liit-tyvä arvo muuttuu — arvo liikkuu kokonaislukuvälillä. Sitävoidaan liikuttaa “tarttumalla palkkiin”,painamalla reunoissa olevia “nuolia” (yksikkösiirtymä) sekä PageUp- ja PageDown-näppäimillä (loh-kosiirtymä). Liukuvalitsimeen liittyen, voidaan asettaaalueen koko, palkin koko, nykyinen arvojasiirtymien koot. Arvon muuttuminen aiheuttaaAdjustmentEvent:n muutoksen, tekotapaa (edellä)heijastaen erilaisia tapahtumia on 5 kappaletta.

Scrollbar() — Konstruktori; arvoväli 0 . . . 100; aluksi 0:ssa; siirtymien koot 1 (yksikkösiirtymä) ja 10(lohkosiirtymä); pystysuuntainen.

Scrollbar(int ori ) — Konstruktori; kuten edellinen, mutta liukuvalitsimen orientaatio onori . Ar-vot: Scrollbar.VERTICAL ja Scrollbar.HORIZONTAL.

Scrollbar(int ori , int v , int vi , int min , int max) — Konstruktori;min . . .max; aluksiv :ssä; palkinkoko vi (samalla lohkosiirtymä). Josmax < min, max := min . Josv ei alueella, tuodaanalueen lähimpään reunaan.

int getOrientation() — Palauttaa orientaation.int getValue() — Nykyinen arvo; mitataan palkin vasemmasta reunasta. Saa arvojamin . . . 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: LuokanScrollbar konstruktoreita ja metodeja.

Esimerkki 5.3 havainnollistaa liukuvalitsimen luontia, havainnointia ja arvojen asettamista. Idea-na 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 nap-pulaa. Liukuvalitsimissa ei ole tapahtumankäsittelijää,vain nappuloissa. Huomaa, miten esimerkis-sä liukuvalitsimet sijoitetaan paneeliin ja nappuloihin liitettäville tapahtumankäsittelijöille kerrotaankonstruktorin kautta, millä liukuvalitsimella operoida.Resetoinnin yhteydessä paneelin komponent-teja haetaan järjestysnumeron perusteella.

Page 59: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

5.5. LIUKUVALITSIN SCROLLBAR 59

Esimerkki 5.3 Scrollbar:n havainnollistus.

import java.awt.∗;import java.awt.event.∗;public classLiukuvalitsinKilpailu {

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 =newButton("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()∗191 + 10);// 10...200int 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 classValintaKuuntelijaimplementsActionListener {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

Page 60: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

60 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA

Kuva 5.4: Liukuvalitsimen havainnollistamista.

5.6 Valintalista List

Valintalista toteutetaan luokanList avulla.List on monipuolisempi kuinChoice. Listalla on esitysik-kuna, jolla on säädettävä koko. Voidaan luoda kahdenlaisialistoja: (a) yksi alkio valittavissa tai (b)useita alkioita voidaan valita. Listaan liittyy tapahtumaItemEvent (alkio valittu, valinta poistettu) jaActionEvent (valittua alkiota kaksoisklikattu; tai enter). LuokkaanChoice verrattuna, metodiIte-mEvent.getItem() palauttaa järjestysnumeron! Esimerkki 5.4 havainnollistaa valintalistan toimintaasaman tapaan kuin esimerkissä 3.3 havainnollistettiin luokkaaChoice. Nyt kuitenkin on mahdollistavalita useita alkioita samanaikaisesti.

Kuva 5.5: Valintalistan havainnollistamista.

List() — Tyhjä lista; ikkunan koko 4. Sallii vain yhden valittavan.List(int r ) — Tyhjä lista; ikkunan kokor. 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 kohdastaind .String[] getItems() — Palauttaa listan kaikki alkiot.void add(String item ) — Lisää alkionitem listan loppuun.void add(String item , int ind ) — Lisää kohtaanind (syrjäyttämättä).void replaceItem(String item , int ind ) — Korvaa kohdanind alkion item :lla.void removeAll() — Tyhjentää listan.void remove(int pos ) — Poistaa alkion kohdastapos .int getSelectedIndex() — Valitun alkion positio, -1: ei yhtä valittua alkiota.int[] getSelectedIndexes() — Palauttaa kaikkien valittujen alkioiden indeksin.

Page 61: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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: LuokanList konstruktoreita ja metodeja.

Esimerkki 5.4 List:n havainnollistus.

import java.awt.∗;import java.awt.event.∗;import java.applet.∗;

public classListTestiextendsApplet {private TextField tf_in =newTextField(20);private TextField tf_out =newTextField(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

classListaKuuntelijaimplementsItemListener {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

Page 62: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

62 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA

5.7 Valintalaatikko CheckBox ja radiopainikkeet

ValintalaatikkoCheckBox on “tekstileima”, johon liittyy tila ja tilalaatikko: on / off, rastitettu tai eirastitettu. Valintalaatikoita voidaan ryhmitelläCheckboxGroup-olion avulla ryhmiksi, joista vain yk-si ko. ryhmän alkioista voi olla kerrallaan valittuna. Tällaisista valintalaatikoiden ryhmästä käytetäännimitystäradiopainikkeettai radionappulat. Samaan ryhmään kuuluvien ei tarvitse olla sijoiteltu “lä-hekkäin” toisiaan — nappulat voivat hyvin olla vaikkapa eriikkunoissa. Radionappuloiden avulla ontarkoitus valita yksi alkioista — tapahtumaItemEvent: alkio valittu, valinta poistettu.

Checkbox() — Tyhjä valintalaatikko (off-tilassa).Checkbox(String label ) — Valintalaatikko leimallalabel (off).Checkbox(String label , boolean s ) — Leima label ja tila s .Checkbox(String label , boolean s , CheckboxGroup g) — Kuten edellä; kuuluu radionappuloi-

den ryhmääng.Checkbox(String label , CheckboxGroup g, boolean s ) — Kuten edellä.Object[] getSelectedObjects() — Palauttaa 1-pituisen listan, jossa leima (valittu) tainull (ei valittu).

⋆ Havainnointimetodit: ’getLabel’, ’getState’, ’getCheckboxGroup.⋆ Modifiointimetodit: ’setLabel’, ’setState’, ’setCheckboxGroup’.

Taulukko 5.10: LuokanCheckBox konstruktoreita ja metodeja.

LuokanCheckboxGroup-olio edustaa vain ryhmän tunnusta. Järjestelmä takaa, että yhdestä ryh-mästä voi valita vain yhden valintalaatikon. Huomaa, mitenCheckBox:n kohdalla kerrotaan, mihinryhmään nappula kuuluu.

CheckboxGroup() — Ainoa konstruktori: luo “tyhjän” olion,jonka identiteettiä käytetään ryhmienerottelussa.

Checkbox getSelectedCheckbox() — Palauttaa sen valintalaatikon ryhmästä, joka on valittuna;null:ei mitään valittuna.

void setSelectedCheckbox(Checkbox box ) — Asettaabox :n valituksi (muut off). Josbox == null,kaikki off-tilaan. Jos ei kuulu ryhmään, ei tapahdu mitään.

Taulukko 5.11: LuokanCheckBoxGroup konstruktoreita ja metodeja.

Esimerkissä 5.5 havainnollistetaan erilaisia valintalaatikoita. Sovellus luo kolme ikkunaa. Kuvan5.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 ikkunaluodaan ’main’-metodissa muuttujaan ’f2’. Nyt kaikki ovatradionappuloita, jotka kuuluvat samaanryhmään (ryhmä luodaan muuttujaan ’g’). Oikeassa yläkulmassa oleva ikkuna on hieman monimut-kaisempi. Siihenkin luodaan radionappuloita, jotka kaikki aluksi kuuluvat ’g1’:n ryhmään. Kuhun-kin radionappulaan liitetään nyt tapahtumankäsittelijä,joka on toteutettu luokalla ValintaKuuntelija.Ideana on, että voitaisiin valita vaihtoehdoista korkeintaan 2. Tämän tapahtumankäsittelijä toteuttaavaihtamalla ensimmäisen valinnan tapahtuessa ko. radionappulan ryhmää — se laitetaan yksin omaanryhmäänsä, jolloin perusryhmästä voidaan vielä valita yksi nappula. Poistotilanteet hoitetaan vastaa-vasti.

Page 63: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

5.7. VALINTALAATIKKO CHECKBOX JA RADIOPAINIKKEET 63

Esimerkki 5.5 Checkbox:n havainnollistus.

import java.awt.∗;import java.awt.event.∗;public classCheckboxTesti {

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 classValintaKuuntelijaimplements 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 (selection26= null ) {

selection1 = selection2; selection1.setCheckboxGroup(g2);selection1.setState(true); selection2 =null ;

}}else if(c == selection2) {

c.setState(false); selection2 =null ;}

}} // itemStateChanged

} // ValintaKuuntelija} // CheckboxTesti

Page 64: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 ohjaavaatietoa — 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ää “sa-mannimistä” 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 sovelluksentoiminta voidaan “estää”, kunnes käyttäjä on ottanut kantaa dialogin “kysymykseen”. Yksinkertaisim-massa muodossa dialogeja käytetään asioiden tiedoittamiseen käyttäjälle — käyttäjän halutaan vain“kuittaavan” tiedoitus luetuksi.

Luokka Dialog perii luokastaWindow. Se on itsenäisten ikkunoiden luokka, joiden avulla teh-dään ns. dialogeja – eli pyydetään käyttäjältä jotain tietoa / valintaa. Tarvittaessa kaikkien muidenGUI:n osien toiminta estyy:modaaliset(valtaavat koko “huomion”) jaei-modaaliset dialogit. Ei-modaalinen dialogi on rinnastettavissa tavalliseen kehykseen. Dialogeihin liittyy otsikko ja reunat.Oletusarvoisesti dialogi on ei-modaalinen ja sen sijoittelumanageri onBorderLayout.

Dialogin käsitteleminen perustuu näkyvyyteen. Dialogi tulee “käsitellyksi”, kun se asetetaan näky-mä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 “nosta-malla” se näkyväksi. Dialogin tällaisen käyttämisen takana on ajatus siitä, että yhtä dialogia voidaankäyttää monta kertaa.

Dialogeilla tulee olla “omistaja”:Frame-olio tai Dialog-olio — viime kädessä tarvitaan siis vä-hintään yksiFrame-olio. Omistajaan liittyvä ajatus on, että modaalinen dialogi estää omistajan toi-minnan. Unix:ssa dialogi-ikkuna avautuu ja sulkeutuu omistajan mukana (ohjaamana).

Dialogien käyttötarkoituksena voi pitää seuraavia asioita: toiminnan valinta, tiedotus ja tiedonhankkiminen. Sovelluksen toiminnan kannalta dialogit ovat hetkellisiä — esillä vain kun sovellus ontietyssä tilassa. Dialogilla ei ole “minimoi” ja “maksimoi” nappuloita (ikkunan yläkulmassa). Luok-kaaDialog käytetään usein perimällä, koska käyttäjä haluaa määritellä mitä komponentteja — eri-tyisesti nappuloita — dialogissa on ja mitä toiminnallisuutta dialogiin liittyy. Asiakassuhteen kautta

Page 65: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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ää teh-dä dialogista kokonaisuus, luokka. ToisaaltaDialog:n aliluokastaFileDialog ei yleensä enää peritä,koska sen toiminta on tarpeeksi monipuolista sellaisenaan.

Dialog(Dialog o) — Konstruktori; tyhjä otsikko; omistajao.Dialog(Dialog o, String t ) — Kuten edellinen; otsikkot .Dialog(Dialog o, String t , boolean m) — Kuten edellinen; modaalisuusm.Dialog(Frame o, String t , boolean m) — Kuten edellinen; omistajaFrame-olio o. Variaatioita.

⋆ Havainnointimetodit: ’getTitle’, ’isModal’ ja ’isResizable’.⋆ Modifiointimetodit: ’setTitle’, ’setModal’ ja ’setResizable’.⋆ ’hide’ ja ’show’.⋆ Lisäksi metodeja luokiltaComponent, Container ja Window.

Taulukko 5.12: LuokanDialog konstruktoreita ja metodeja.

Kuva 5.7: “Köyhän miehen” Minefield-peli — havainnollistaadialogeja.

Page 66: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

66 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA

Esimerkki 5.6 Minefield.java — dialogien havainnollistus.

import java.awt.∗;import java.awt.event.∗;public classMinefieldextendsFrame {

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(newGridLayout(5,4));for (int i=0; i<20; i++) {

Button b =new Button("" +(i+1)); p.add(b);b.addActionListener(newActionListener(){

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

}

classGameOverNewTryextendsDialog {GameOverNewTry(Frame f, String s1, String s2) {

super(f, s1, true); setSize(150,75);add(newLabel(s2), BorderLayout.NORTH);Button b1 =new Button("Yes" );Button b2 =new Button("No" );add(b1, BorderLayout.WEST); add(b2, BorderLayout.EAST);b1.addActionListener(newActionListener(){

public void actionPerformed(ActionEvent e) {restartGame(); setVisible(false); dispose(); }});

b2.addActionListener(newActionListener(){public void actionPerformed(ActionEvent e) {

dispose(); System.exit(0); }});pack(); setVisible(true);

} //} // GameOverNewTry

} // Minefield

Page 67: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

5.9. TIEDOSTON VALINTAIKKUNA FILEDIALOG 67

Esimerkissä 5.6 luodaan4 × 5 nappulaa ja jokaiseen nappulaan liitetään arpomalla tietoonkosiinä “miina” (riippumattomasti, 15% todennäköisyydellä; konstruktorin lopussa). Konstruktorin for-silmukassa nappulat luodaan ja jokaista kohti luodaan anonyymissä sisäluokassa tapahtumankäsitte-lijä, joka liitetään nappulaan. Käsittelijän merkitys on painnalluksen seurauksena tutkia, oliko nap-pulan “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, jossaonnitellaan kentän ratkaisemisesta.

Esimerkkiä 5.6 on paikallaan hieman kritisoida. Ensinnäkin ’main’-metodin sisällä oleva anonyy-mi 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 kiinnit-tää yksi ja sama tapahtumankäsittelijäolio. Lisäksi, käsittelijän sisällä luodaan onnistumis- ja epäon-nistumistilanteissa ’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ää toiminnal-lisuus tapahtumankäsittelijän muodossa — kummankin toteuttaminen anonyyminä sisäluokkana onperusteltua, koska kumpikin on toiminnaltaan “lyhyt”. Luokan ’GameOverNewTry’ konstruktorissaon kuitenkin yksi asia tehty huonosti: konstruktorin lopussa dialogi-ikkunasta tehdään näkyvä. Tätätoimintaa ei kannattaisi sitoa luontioperaatioon.

5.9 Tiedoston valintaikkunaFileDialog

Luokalla FileDialog toteutetaan hyvin monipuolinen tiedoston nimen valintatyökalu. LuokkaFile-Dialog on dialogi, ja se perii luokastaDialog. Ideana on pyytää käyttäjäävalitsemaan tiedoston nimi,kirjoittamistatai lukemistavarten. Työkalu osaa navigoida allaolevassa tiedostojärjestelmässä. Työka-lua 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, elise 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. Setuodaan esiin ’setVisible(true)’:lla ja se poistuu, kun valinta on tehty! FileDialog vaatii omistajao-liokseen kehyksen.

FileDialog(Frame o) — Konstruktori; tyhjä otsikko; omistajao. Lukeminen!FileDialog(Frame o, String t ) — Kuten edellinen; otsikkot .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 luokiltaComponent, Container, Window ja Dialog.

Taulukko 5.13: LuokanFileDialog konstruktoreita ja metodeja.

Esimerkillä 5.7 (kuva 5.8) havainnollistetaan tiedoston nimen valitsemista. Pääikkunan nappuloi-den klikkaamisen seurauksena syntyy valintatyökaluikkuna, jonka avulla voidaan tiedoston nimi vali-ta. Huomaa, miten valitun tiedoston nimi selvitetään dialogi-oliosta sen näkymättömäksi muuttumisenjälkeen.

Page 68: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classFileChooser {

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 classBAction implementsActionListener {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

Page 69: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

5.10. MENUPALKKI MENUBAR JA SIIHEN LIITTYVÄT LUOKAT 69

5.10 Menupalkki Menubar ja siihen liittyvät luokat

Menupalkki on hyvin tuttu monista sovelluksista. Menupalkki koostuu alasvedettävistä menuvali-koista. Kukin menuvalikko puolestaan koostuu alkeisalkioista tai alimenuista. Menuvalikot ovat GUI-komponenttien tapaisia, mutta esiintyvät vainFrame:n osanaMenuBar:ssa tai yleensä ponnahdusva-likoissa (popup menus). Menuiden hierarkkisen rakenteen alkeisalkioita ovat leimat, valintalaatikot jaeroittimet (vaakasuuntainen viiva). Menun osiinvoidaan liittää tapahtumankäsittelijöitä kuten GUI-komponentteihinkin. Lisäksi AWT:n menuihin liittyy mahdollisuus tehdä valintoja pikanäppäimillä.

Menuihin liittyvät ainakin seuraavat AWT:n luokat:MenuBar, MenuComponent Menu, Me-nuItem, MenuContainer,CheckboxMenuItem,PopupMenu,MenuShortcut. Näiden suhdetta toi-siinsa perimysmielessä on havainnollistettu kuvassa 5.9.

MenuComponent

MenuBar MenuItem

CheckboxMenuItem Menu

PopupMenu

MenuContainer

Kuva 5.9: Menuihin liittyvien luokkien hierarkia.

Luokka MenuComponent on abstrakti: se määrittelee menualkioiden yleiset ominaisuudet, jasiten se on verrattavissa luokkaanComponent. Luokka määrittelee fontin ja nimen käsittelyn sekäAWT-tapahtumankäsittelyn delegointimetodit.

LuokkaMenuBar on konkreetti luokka ja se määrittelee menupalkin. Sellaisen voi liittää osaksiFrame-oliota. Menupalkki sisältää listanMenu-olioita — tavallaan palkki on säiliö. Menupalkkiinsuoraan ei kuitenkaan voi liittää tapahtumankäsittelijöitä. Tavallisten menuiden lisäksi menupalkkiinvoi liittyä erityinen help-menu.

LuokkaMenuItem esittää menualkioita, joita ovat “leima”, menuvalikko ja valintalaatikko. Sel-laisenaanMenuItem edustaa leimaa — muut toteutetaan aliluokkienCheckboxMenuItem ja Menukautta. Menualkioon kohdistuu tapahtumaActionEvent, kun se valitaan.

LuokanCheckboxMenuItem avulla esitetään valintalaatikkoa, joka on osana menuvalikkoa. Sesisältönä on leima ja tila. Valittaessa valintalaatikko tuottaaItemEvent:n. Konstruktorien muoto ontavanomainen ja havainnointi- sekä muutometodit käsittelevät tilaa ja leimaa.

Luokan Menu avulla esitetään alasveto(menu)valikkoa. TavallaanMenuItem vs Menu -suhdeon samanlainen kuinComponent vs Container -suhde. Sisällöltään menu on pelkkä menualkioidenlista.

Luokan MenuShortcut avulla esitetään pikavalintoja. Luokka käyttääKeyEvent:ssä määritel-tyjä arvoja merkkien esittämiseen (esim.KeyEvent.VK_A (’A’), KeyEvent.VK_F1 (F1), KeyE-vent.VK_AT (’’), KeyEvent.VK_LEFT_PARENTHESIS (’(’), . . . ). Painamalla ctrl+merkki, tapah-

Page 70: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

70 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA

MenuBar() — Konstruktori, joka luo tyhjän palkin.Menu add(Menu m) — Lisää menunmpalkin loppuun.Menu getMenu(int ind ) — Palauttaaind:n menun.void remove(int ind ) — Poistaaind: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 menupal-

kin alkioihin.MenuItem getShortcutMenuItem(MenuShortcut s ) — Palauttaa sen menualkion, johon liittyy pi-

kanäppäins .void deleteShortcut(MenuShortcut s ) — Poistaa pikanäppäimens .

Taulukko 5.14: LuokanMenuBar konstruktoreita ja metodeja.

MenuItem(String n) — Tekee menualkion, jolla nimin.MenuItem(String n, MenuShortcut s ) — Kuten edellinen, mutta lisäksi pikanäppäins .

⋆ Havainnointi ja muuttaminen: ’getLabel’, ’setLabel’, ’isEnabled’, ’setEnabled’, ’getShortcut’,’setShorcut’, ’deleteShortcut’, ’getActionCommand’, ’setActionCommand’ (oletus on leima).

Taulukko 5.15: LuokanMenuItem konstruktoreita ja metodeja.

tuu pikavalinta.

Esimerkissä 5.8 muodostetaan kaksi tavallista menuutaFrame-olioon liitettävään menupalkkiin.Lisäksi palkkiin liitetään help-menu. Ensimmäinen menuista sisältää joukon ilmeisesti tiedostoihinliittyviä kohtia — niihin ei kuitenkaan sovelluksessa ole liitetty muuta toiminnallisuutta kuin sovel-luksen lopettaminen. Toinen menu koostuu yhdestä alivalikosta. Kyseisen menun jokaiseen kohtaanon liitetty toiminnallisuus: valinta muuttaa kehyksen taustaväriä. Huomaa, miten tämä toteutetaanluokan ’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 niminame (menupalkissa).MenuItem add(MenuItem mi ) — Lisää menualkionmi 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: LuokanMenu konstruktoreita ja metodeja.

Page 71: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

5.10. MENUPALKKI MENUBAR JA SIIHEN LIITTYVÄT LUOKAT 71

Esimerkki 5.8 Menupalkin muodostaminen.

import java.awt.∗;import java.awt.event.∗;public classMenuDemo {

public static void main(String[] args) {Frame f =newFrame("MenuDemo" ); f.setSize(400,200);Menu m1 =newMenu("File" );MenuItem openW =

newMenuItem("Open" , newMenuShortcut(KeyEvent.VK_O));MenuItem closeW =newMenuItem("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 =newMenu("Väri" );MenuItem i1, i2, i3, i4;i1 = new MenuItem("Punainen" );i2 = new MenuItem("Sininen" , newMenuShortcut(KeyEvent.VK_S));i3 = new MenuItem("Keltainen" );i4 = new MenuItem("Vihreä" , newMenuShortcut(’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 =newMenu("Lisää" );m3.add(i3); m3.add(i4);m2.add(i1); m2.add(i2); m2.addSeparator(); m2.add(m3);MenuBar palkki =newMenuBar();palkki.add(m1); palkki.add(m2);Menu help =newMenu("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 classMenuKuuntelijaimplementsActionListener {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} //

Page 72: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

72 LUKU 5. LISÄÄ AWT-KOMPONENTTEJA

5.11 Ponnahdusvalikko

Luokalla PopupMenu toteutetaan ns. ponnahdusvalikot. Kuten kuvasta 5.9 nähdään, PopupMenuon luokanMenu aliluokka — ponnahdusvalikoita voidaan käyttää myös menupalkissa. Ponnahdus-valikko ponnahtaa esiin jonkin tapahtuman laukaisemana: esim. klikattaessa komponettia oikeanpuo-leisella hiirinäppäimellä. Vaikka ponnahdusmenun voi liittää mihin tahansa komponenttiin ja matalantason tapahtumien yhteydessä voidaan selvittää, onko kyseinen tapahtuma sellainen, joka allaolevassaikkunointijärjestelmässä laukaisee ponnahdusvalikon esiin ponnahtamisen, niinponnahtaminen pitäätehdä itse tapahtumankäsittelijällä! Lisäksi, kun ponnahdusvalikko nostetaan esiin, niin sen ’show’-metodissa pitää kertoa, minkä komponentin suhteen valikkonostetaan esiin (vaikka se olisi kiinnitettyjohonkin komponenttiin). MuutoinPopupMenu on kuinMenu. Esimerkissä 5.9 luodaan ponnahdus-valikko, jonka nostaminen esiin tehdään kiinnittämällä kehysolioon hiirtapahtumien kuuntelija, jokareagoi 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 classPopupDemo {

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 classPopupKuuntelijaextendsMouseAdapter {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)6= 0) menu.show(kohde, 20, 20);}/∗ Tai toisin ...public void mousePressed(MouseEvent e)

{ if (e.isPopupTrigger()) menu.show(kohde, 20, 20); }∗/

} // PopupKuuntelijastatic classMenuKuuntelijaimplementsActionListener {

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

Page 73: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

Luku 6

GUI-sovellusten rakenteesta

Edellisissä luvuissa on käyty läpi tapahtumankäsittely jaAWT:n komponentit. Tässä luvussa pohdi-taan, miten niiden avulla tulisi rakentaa GUI-sovelluksia. Luvussa 6.2 pyritään “pilkkomaan” GUI-sovelluksen muodostaminen hallittavan kokoisiin vaiheisiin. Kehittäminen perustuu ajatukselle, ettäGUI-sovellusten voidaan nähdä toimivan tila-automaattien tapaan siten, että automaatin transitioitavastaavat 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ä demonstroidaantekemällä luvussa 6.4 MasterMind-peli, jolla on graafinen käyttöliittymä1. Peliä voi käyttää sekä GUI-sovelluksena että applettina. Ennen pelin muodostamista,luvussa 6.3 tarkastellaan, miten kannattaisitehdä sovellus, joka toimii samanaikaisesti sekä GUI-sovelluksena että sovelmana.

MasterMind on kahden pelattava lautapeli. Pelin idea on, että ensimmäinen pelaaja (jatkossa ar-vuuttelija) ensin asettaa neljään “kohtaan” valitsemansaväriset nappulat, minkä jälkeen toinen pelaa-ja (jatkossa pelaaja) yrittää selvittää arvuuttelijan kätkemien nappuloiden värit ja järjestyksen. Kunpelaaja yhden pelivuoron aikana ehdottaa ratkaisuaan ohjelman valitsemien nappuloiden väreiksi, ar-vuuttelija muodostaa pelaajalle vastauksen, joka kertoo,miten hyvä arvaus oli. Vastauksessa käyte-tään kahdenvärisiä “nappuloita”, mustia ja valkoisia. Yksi musta tarkoittaa, että yksi oikean värinennappula on ollut oikeassa paikassa. Kaksi mustaa tarkoittaa, että arvauksessa on kaksi nappulaa oi-keilla paikoilla. Neljä mustaa nappulaa puolestaan tarkoittaa, että pelaaja on ratkaissut arvuuttelijanarvoituksen. Valkoinen puolestaan tarkoittaa, että ehdotuksessa on oikean värinen nappula, mutta seon 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ä se-kä arvuuttelijan värien, pelaajalle sallittavien arvausten että arvuuteltavien nappuloiden lukumäärätvoivat vaihdella tietyissä rajoissa.

6.1 GUI-sovellus tilakoneena

Tähän mennessä on opittu, että GUI-sovellukset rakennetaan muodostamalla alustusvaiheen seurauk-sena sovellus, johon on aseteltu GUI-komponentit halutulla tavalla ja sovelluksen toiminnallisuus onmuodostettu liittämällä GUI-komponentteihin sopivalla toiminnallisuudella varustettuja tapahtuman-käsittelijöitä. Sovellusta voidaan helposti ajatella automaattina, jolla on kullakin hetkellätila. Tila onsama kuin sovelluksen tietosisällön arvo kullakin hetkellä — tietosisältö tarkoittaa kaikkia niitä olioi-ta ja arvoja, joihin sovelluksesta päästään käsiksi.Tapahtumien seurauksena sovelluksen tila muuttuu

1Ohjelmointi I kurssin materiaalin puitteissa on rakennettiin samainen peli tekstipohjaisella käyttöliittymällä. Nyt tehtä-vä peli on paljon monipuolisempi.

73

Page 74: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

74 LUKU 6. GUI-SOVELLUSTEN RAKENTEESTA

— esimerkiksi, esimerkissä 4.1 tapahtumankäsittelijöiden avulla muutettiin tekstikentässä esitettävänlaskurin arvoa.

Yleisesti voitaisiin ajatella, että GUI-sovelluksella onhyvin suuri määrä erilaisia tiloja (kaikkierilaiset lailliset ja mahdolliset tietosisällön arvot).Tapahtumankäsittelijät määrittelevät tällaisenää-rellisen automaatintilojen välisettransitiot, eli tilasiirtymät. Tämä ajattelutapa on sikäli ongelmalli-nen, että tiloja on todella paljon ja yksi tapahtumankäsittelijä implisiittisesti määrittelee hyvin montatransitiota.

Toinen lähestymistapa on mallintaa tilatmetatiloinatai jopa metatilojen (osatilojen) tulona. Meta-tilalla tarkoitetaan tässä yhteydessä alkeistilojen joukkoa, johon liittyysama joukko aktiivisia tapah-tumankäsittelijöitäja ärsykkeiden (tapahtumien) tuottamismahdollisuuksia.Esimerkiksi, jos jonkinsovelluksen suorituksen aikana nostetaan esiin modaalinen dialogi-ikkuna, sovelluksen voidaan kat-soa 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 ilmaisemaansovelluksen tilaa, johon liittyy tietynlainen toiminnallisuuden mahdollisuus.

Metatilojen tulon ajatus liittyy siihen, että iso sovellusvoi koostua osista, jotka vaikuttavat toisiin-sa hyvin vähän (oikeastaan pitäisi vaatia, että osien välistä vuorovaikutusta ei ole lainkaan). Kutakinosaa voidaan pitää yhtenä(meta)osatilanaja kokonaisuutta osatilojen tulona. Tuolloin sovelluksenmetatiloja 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äli-siä tilasiirtymiä. Tämä helpottaa sovelluksen suunnittelua huomattavasti, sillä metatilat usein muo-dostuvat täysin erillisistä GUI-komponenteista ja niihinliitetyistä tapahtumankäsittelijöistä. Metatilatusein samaistuvat toteutusvaiheessa päätason ikkunoihin(kehyksiin, dialogeihin). Lisäksi metatiloi-hin 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 ad-jektiivien hyvyys on valitettavasti usein kovin subjektiivista, mutta monia yleisiäkin asioita on todet-tavissa. Tässä luvussa pyritään kuitenkin vain antamaan teknisiä neuvoja koskien kysymystä, mitenrakentaa 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 jatämä “jotakin” on sovelluksen tietosisältö, jota varten ovat omat tietorakenteensa. Yksinkertaisimmil-laan tietorakenne on joukko perustyyppien mukaisia luokka- ja/tai instanssimuuttujia. Toisaalta hyvin-kin monimutkaisia tietorakenteita saatetaan käyttää — ja osaa tiedoista voidaan ylläpitää tiedostoissa,tietokannoissa tai esimerkiksi verkon kautta käytettävissä tietovarastoissa.

Page 75: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

6.2. MITEN RAKENTAA GUI-SOVELLUS? 75

Luettelon kolme kohtaa muodostavat tunnetun kolminaisuuden:Model-View-Controller, eli MVC.Itse asiassa MVC on yleinensuunnittelumalli. Ideana on erottaa selkeästi toisistaan systeemin ti-la (model), graafisesta ulkoasusta (view) ja sitä hallitsevasta toiminnallisuudesta (controller). JavanAWT-pohjaisten GUI-sovellusten tapauksessa toiminnallisuus on joukko tapahtumankäsittelijöitä2.Vastaavasti ’view’ on sovelluksen ulkoasu. Malli (model) on erillinen osa (joukko luokkia), joidentehtävänä on esittää tilaa ja mahdollistaa erilaiset lailliset tilan muutokset. ’View’:n tehtävänä on esit-tää kulloinenkin ulkoasu vastaten mallin tilaa. ’Controller’:n tehtävä on mahdollistaa tilan muutoksetja 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ä johtuenMVC:n väliset vuorovaikutukset esitetään kuvan 6.1 mukaisesti — ottamalla kantaa ajantasaisuudentekemiseen, osa kaarista voidaan poistaa.

Controller

ViewModel

Kuva 6.1: MVC:n osat.

MVC-ajattelun perusteella luokkia syntyy tapahtumankäsittelijöistä, sovelluksen tilan esittämi-sestä ja sovelluksen tilan yhdistämisestä ulkoasuun. Viimeisin tarkoittaa luokkia, joilla esitetään (taimuodostetaan) GUI-komponenttien sommiteltuja kokoelmiasiten, että niihin liittyy haluttu toimin-nallisuus tapahtumankäsittelijöiden muodossa. MVC:n osien muodostamisen kannalta olennaisia ky-symyksiä ovat

• miten uudelleenkäytettäviä osat ovat?

• tulisiko GUI-komponenttien kokonaisuudet muodostaa asiakas- vai perintäsuhteen avulla (Fra-me:n käyttö)?

Mallin tulee olla GUI-osuudesta riippumaton, joten se on usein uudelleenkäytettävissä. Usein GUI-kokonaisuudet rakentuvat ikkunoiden ympärille — jos jokinikkuna on yleiskäyttöinen, siitä kannattaaperimällä muodostaa luokka. Muutoin asiakas- ja perimyssuhteen välinen kysymys on lähinnä tekni-nen ja ohjelmointityylikysymys. Kokonaisuuden hallitsemiseksi osa luokista usein kannattaa toteuttaasisäluokkina: jopa malli voitaisiin ajatella toteuttaa sisäluokkana, joskin yleensä lähinnä tapahtuman-käsittelijät toteutetaan sisäluokkina.

2Tilannetta on tosin mahdollista rikastuttaa ottamalla käyttöön useita säikeitä, esimerkiksi animoinnin toteuttamiseksi.

Page 76: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

76 LUKU 6. GUI-SOVELLUSTEN RAKENTEESTA

Edellä MVC:n yhteydessä ajateltiin, että käyttöliittymänkäyttäjä on ainoa taho, joka vaikuttaa’controller’-osan kautta. Tilanne voi kuitenkin olla monimutkaisempi: useita säikeitä ja toisia sovel-luksia verkkoyhteyksien3 kautta.

6.3 Appletti ja sovellus yhdessä

Luokka voi toimia samanaikaisesti sekä sovelluksena ja sovelmana(applettina). Tuolloin luokallaon sekä ’main’-metodi että se perii luokastaApplet (suoraan tai epäsuorasti). On varsin kätevää,jos ohjelmaa voidaan vaihtoehtoisesti suorittaa GUI-sovelluksena tai applettina. Seuraavassa pyritäänmuodostamaan perusteltu resepti eri osien rakentamiseksitällaisessa tilanteessa.

Yksinkertaisissa tapauksissa sovelluksen komponentit muodostetaan ’main’-metodissa kun taasappleteilla vastaava metodi on ’init’, jota kutsutaan kerran appletin luonnin jälkeen. Ilmeisestikinkannattaa muodostaayhteinen 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ä applettion perimyssuhteiden takia myösPanel-tyyppinen komponenttisäiliö, jolloin se voidaan sijoittaa suo-raan osaksi kehystä (Frame). Toinen tapa ratkaista ongelma on, että luodaan yhteisessä alustusosassaPanel-tyyppinen komponenttisäiliö, joka sijoitetaan ’init’:ntapauksessaApplet-olioon ja ’main’:ntapauksessaFrame-olioon.

Yhteisen alustusosan muodostamisen kannalta on vielä toinenkin ongelma:tiedonvälitystavat ovaterilaisia. Metodissa ’init’ voidaan tietoa hakea HTML-sivulta välitetyistä parametreista kun taas ’main’:ntapauksessa tietoa välitetäänString[]-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-ikkunatnimittä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 esit-tävä kehysolio.

Mikä sitten on yhteinen osa? Saattaisi houkutella muodostaa yhteiseksi osaksi appletin konstrukto-ri, mutta se ei ole toimiva ratkaisu, sillä appletin konstruktorilla ei ole käytettävissään tietoa appletinympäristöstä (HTML-tiedostosta välitettävät parametrit). Tällainen “yhteysolio” liitetään applettiinselaimen toimesta vasta välittömästi luonnin jälkeen (ja ennen ’init’:n kutsumista). Yhteinen osuus onsiis 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 seurauksenakaikki sovelluksen säikeet ’kuolevat’. Applettien kohdalla tilanne on vaikeampi: applettia nimittäinsuoritetaan esimerkiksi selaimeen upotetussa virtuaalikoneessa ja kyseinen virtuaalikone voi suorittaasamanaikaisesti useita muita appletteja. Itse asiassa päätös sovelman suorituksen lopettamisesta kuu-luu pikemminkin selaimelle kuin sovelmalle itselleen. Mahdollisia tapoja “lopettaa” appletti sen it-sensä toimesta ovat kaikkien tapahtumankäsittelijöiden poisto tai kaikkien komponenttien passivointi.Edellinen kuitenkin tarkoittaa, että lopettaminen pitää suorittaa tilanteesta riippuen eri tavalla ja lopet-taminenvaatii tietoa siitä suoritetaanko luokkaa applettina vai sovelluksena. Kyseinen tieto voidaanluoda helposti ’init’- ja ’main’-metodeissa.

3Asiakas-palvelin sovelluksissa GUI-sovellukseen liittyvä malli on usein suurelta osin palvelimessa. Esimerkiksi,jospalvelin on “pelipöytä” ja asiakas on “pelaaja”, niin pelintilaa kuuluu ylläpitää palvelimessa.

Page 77: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

6.4. ESIMERKKISOVELLUS MASTERMIND 77

6.4 Esimerkkisovellus MasterMind

Miten tehdä (tämän luvun alussa kuvattu) MasterMind-peli?Haluttaisiin saada aikaan luokkia, joi-den avulla MasterMind:a voitaisiin suorittaa sovelluksena (kuva 6.2) ja sovelmana (kuva 6.3). Kum-massakin kuvassa yksi rivi koostuu pelaajan tekemistä valinnoista (neljä nappulaa vasemmassa reu-nassa); nappulasta, jolla ilmoitetaan värivalinnat tehdyiksi; sekä valinnan seurauksena arvuuttelijanvastineesta (enintään neljä mustaa ja/tai valkoista nappulaa oikeassa reunassa). Rivien määrä kertoomontako arvausta pelaajalla on. Oikea vastaus on kummassakin kuvassa ratkaisemisen jälkeen pal-jastettu pääruudun alareunassa. Peli päättyy joko onnistumiseen tai epäonnistumiseen: oikeat väritlöytyvät tai kaikki arvausrivit käytetään. Kummassakin tapauksessa tilanteesta informoidaan pelaajaadialogi-ikkunan avulla.

Kuva 6.2: MasterMind-peli sovelluksena.

Sovelluksen toiminta pitäisi nyt pääpiirteissään olla kiinnitetty, mutta miten se toteutetaan? Kos-ka 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 kokonaisuuksik-si. 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 sovelluk-sen toteuttavat luokat.

Kuvassa 6.4 on muodostettu malli MasterMind-pelin metatiloista ja niiden välisistä transitioista.

Page 78: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

78 LUKU 6. GUI-SOVELLUSTEN RAKENTEESTA

Kuva 6.3: MasterMind-peli applettina.

Transitiot eivät ole vain suoraviivaisia siirtymiä tilasta toiseen, vaan mahdollisesti ehdollisia, jolloinsiirtymän kohde riippuu sovelluksen tilasta. Kaikki pyöreäreunaisista laatikoista (kuvaavat metatila-arvoja) lähtevät nuolet ovat tapahtumankäsittelijöitä. Katkoviivalla rajattu osuus muodostaa yhdentapahtumankäsittelijän toiminnallisuuden — se on suurehko, koska sen toiminta riippuu ehdollisestisovelluksen tilasta. MasterMind-pelin tapauksessa metatilat esittävät pääasiallisesti komponenttisäi-liöitä — niihin liittyvä toteutus on merkitty laatikon ulkopuolelle oikeaan yläreunaan.

MasterMind-pelin malli ylläpitää tietoa rivikohtaisestitehdyistä värivalinnoista sekä myös arvuu-teltavasta rivistä. Värivalintojen tekemisen jälkeen mallin tehtävänä on laskea, montako mustaa javalkoista nappulaa tehty värivalinta implikoi. Esimerkissä 6.1 esitettävä mallin toteuttava koodi ontehty yleiseksi rivien, pelaajan nappuloiden värien lukumäärän sekä arvuuteltavien nappuloiden luku-määrän suhteen. Tieto näiden arvoista on myös osa mallin tietosisältöä. Mallissa on tehty yksi pelintilan esittämistä koskeva kiinnitys: värejä esitetään numeroilla 1 . . . colors.

Konstruktoreilla tietosisältö alustetaan — arvuuteltavarivi arvotaan samalla. Huomaa, että mallin

Page 79: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

6.4. ESIMERKKISOVELLUS MASTERMIND 79

valitaan värejä(perustila)

värin asetus

Virhetila:osa harmaita

initmain

alustus

väri valittu

PopupMenu

aseta väri

ok

kaikki asetettu?

ratkaistu?

jatkettavissa?

ei

kyllä

ei

kyllä

eiGame Over

Game OverSolved!

Failed!

uudestaan

uudestaan

lopeta

lopeta

kyllä

värit asetettu

- generoi uusi rivi- estä edellisen rivin toiminta

- resetoi peli

- resetoi peli

- lisää rivi malliin- mustia?- valkoisia?

Dialog

Dialog

Dialog

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 havainnoimi-nen tulee mahdollistaa. Malli tarjoaa luonnollisesti metodin ’setNextGuess’ uuden rivin kiinnittämi-seksi sekä suurehkon joukon metodeja mallin — eli pelin tilan — havainnoimiseksi. Huomaa, mitenarvuuteltavan rivin asettamisen yhteydessä tulee myös “nollata” arvatut rivit. Malli on toteuttu niin,että uusia arvausrivejä voi lisätä senkin jälkeen kun oikearivi on jo löytynyt. Tämä kuitenkin huo-mioidaan metodissa ’isSolved’.

Page 80: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

80 LUKU 6. GUI-SOVELLUSTEN RAKENTEESTA

Esimerkki 6.1 MasterMindModel.java — pelin malli.

public classMasterMindModel {// Luokka esittää MasterMind-pelin tilaapublic 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 riviprivate 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=> Exceptionif (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.length6= 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 . . .

Page 81: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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.length6= 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]6= 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 booleancanStillGuess() {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

Page 82: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 HTML-tiedostosta välitettyjen parametrien arvot (muodostaen niistä kokonaislukuja); merkitään talteen (so-velluksen lopettamista varten), että ollaan suorittamassa applettia; luodaan “dummy”-kehys dialogejavarten; ja lopuksi kutsutaan yhteistä alustusosuutta, metodia ’initialize’. Metodissa ’main’ toimitaansamaan tapaan. Luodaan ensin applettiolio; luetaan välitetyt parametriarvot; luodaan kehysolio aset-taen applettiolio kehykseen (toimii, koska se on samallaPanel); alustetaan applettiolio ’initialize’:lla;ja tehdään kehysoliosta näkyvä.

Yhteisessä alustusosassa ’initialize’ luodaan ensin malli käyttäen välitettyjä arvoja, jos ne vainovat laillisia. Tämän jälkeen luodaan pääikkunan kaikki komponentit — jokaiselle riville luodaankaikki nappulat ja itse asiassa ratkaisukin laitetaan jo valmiiksi pääikkunan alareunaan. Näistä GUI-komponenteista suurin osa laitetaan kuitenkin näkymättömiksi. Ainoastaan ensimmäisen rivin vasem-massa reunassa olevat nappulat sekä keskellä oleva nappulatehdään näkyviksi. Metodin ’initialize’ en-simmäisessä for-silmukassa rivit ketjutetaan (’gr.setNext(gr1);’), jotta seuraavalle riville siirtyminenosataan tehdä tapahtumankäsittelijöillä. Metodin ’initialize’ loppuosassa luodaan vielä kolme dialogi-ikkunaa. Muuttujaan ’allColorsNotSetDialog’ luodaan dialogi, jolla kerrotaan, että keskellä olevaanappulaa painettaessa, kaikkien vasemman puoleisten arvausta edustavien nappuloiden värejä ei oltuvielä asetettu. Huomaa, miten dialogi-ikkunaan luodaan ’ok’-nappula ja siihen kiinnitetään tapahtu-mankäsittelijä (joka “poistaa” dialogin). Metodin kahdella viimeisellä rivillä luodaan vielä dialogi-ikkunat 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 taus-tan 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ää eiole estetty. Passivointi tarkoittaa koko nappulan ’toiminnan estämistä’, samalla estäen käsittelijän toi-minta. 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 aktivoidenvasemmalla puolella olevat nappulat — oikealla puolella oleviin ei kiinnitetä tapahtumankäsittelijöitä.Suurimman osan konstruktorista muodostaa rivin keskellä olevaan nappulaan kiinnitetty tapahtuman-käsittelijän koodi. Tämä käsittelijä on kuvassa 6.4 katkoviivalla ympäröity tapahtumankäsittelijä. Ku-ten käsittelijän laajuudesta voidaan päätellä, ohjelmassa on tehty virheellinen suunnittelupäätös, kunkyseinen 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äättymi-sestä kertovien modaalisten dialogien luokka ’GameOverDialog’. Huomaa, miten dialogiin asetet-tuihin nappuloihin kiinnitetään tapahtumankäsittelijät. Molemmat toteutetaan anonyymeinä sisäluok-kina: ensimmäinen lopettaa pelin (huomioiden, miten sovellukseen on tultu) ja toinen resetoi pelinaloittaen samalla uuden pelin.

Page 83: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

6.4. ESIMERKKISOVELLUS MASTERMIND 83

import java.awt.∗;import java.awt.event.∗;import java.applet.∗;public classMasterMindGameextendsApplet {

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 booleanisApplet;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 =newFrame("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 componentstry {

game =newMasterMindModel(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 =newGameRow[rows];GameRow gr =newGameRow();p1.add(gr);gr.setVisible(true);gameRows[0] = gr;for (int i=1; i<rows; i++) {

GameRow gr1 =newGameRow();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 panelsolution =newColoredButton[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 =newColoredButton(int2color(sol[i]));solution[i] = cb;cb.setVisible(false);p2.add(cb);

}p2.setVisible(true);add(p2, BorderLayout.SOUTH);// allColorsNotSetDialogallColorsNotSetDialog =newDialog(f,"Error" ,true);allColorsNotSetDialog.setSize(250,80);allColorsNotSetDialog.setLocation(100,100);allColorsNotSetDialog.setLayout(newBorderLayout());allColorsNotSetDialog.add(newLabel("All colors not set!" ),

BorderLayout.CENTER);Button ok =newButton("Ok" );ok.addActionListener(newActionListener(){

public void actionPerformed(ActionEvent e) {allColorsNotSetDialog.setVisible(false);

}});allColorsNotSetDialog.add(ok, BorderLayout.SOUTH);// gameOverDialoggameOverSolvedDialog =newGameOverDialog(f,true);gameOverUnsolvedDialog =newGameOverDialog(f,false);

} // initialize

public classColoredButtonextendsButton {private PopupMenu popup;public ColoredButton() {this(Color.gray); }

public ColoredButton(Color c) {super(); setBackground(c);

} // ColoredButton()

public void setActive() {popup =newColorChooser(this);add(popup);addActionListener(newActionListener(){

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 booleanisColorSet() {return (getBackground()6= Color.gray);

} // isColorSet} // class ColoredButton

Page 84: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

84 LUKU 6. GUI-SOVELLUSTEN RAKENTEESTA

public classGameRowextendsPanel {private GameRow next;private ColoredButton[] result;private ColoredButton[] buttons;private Button allSetB;private Panel left,right;

public GameRow() {setLayout(newGridLayout(1,0,5,5));left = newPanel();left.setLayout(newGridLayout(1,0));buttons =new ColoredButton[perRow];for (int j=0; j<perRow; j++) {

ColoredButton cb =newColoredButton();cb.setActive();left.add(cb);buttons[j] = cb;

}left.setVisible(true);add(left);allSetB =new Button("Colors set" );allSetB.addActionListener(newActionListener(){

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 setfor (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 = newPanel();right.setLayout(newGridLayout(1,0));result =new ColoredButton[perRow];for (int j=0; j<perRow; j++) {

result[j] = newColoredButton();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 classColorChooserextendsPopupMenu {

public ColorChooser(Button b) {super("Choose color" );for (int i=0; i<colors; i++) {

MenuItem mi =newMenuItem(COLORNAMES[i]);add(mi);mi.addActionListener(newColorChoiceListener(b,

COLORS[i]));}

} // ColorChooser()

} // ColorChoose

public classColorChoiceListenerimplementsActionListener {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 classGameOverDialogextendsDialog {private Label text;

public GameOverDialog(Frame f,booleanb) {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 =newButton("Quit" );add(again, BorderLayout.WEST);add(quit, BorderLayout.EAST);quit.addActionListener(newActionListener(){

public void actionPerformed(ActionEvent e) {if (isApplet) {

GameOverDialog.this.setVisible(false);MasterMindGame.this.setEnabled(false);return ;

}else{ System.exit(0); }

}});again.addActionListener(newActionListener(){

public void actionPerformed(ActionEvent e) {resetGame();GameOverDialog.this.setVisible(false);

}});} // GameOverDialog(Frame f, boolean b)

} // class GameOverDialog

} // MasterMindGame

Page 85: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

Luku 7

Johdatus Swing-sovelluksiin

Graafinen Swing-kirjasto tuli Javan yhteyteen ns. Java Foundation Classes (JFC) -kokonaisuudenmyötä. Swing ei ollut vielä osa JDK 1.1:tä, mutta sitä voitiin soveltaa sen yhteydessä JFC:n kaut-ta. JDK 1.2:sta eteenpäin Swing on ollut osa JDK:ta. Swingintaustalla on Sunin lisäksi Netscape-yhtiö. AWT:n ominaisuudet nähtiin monien toimesta riittämättömiksi — niillä ei saanut aikaan kaik-kea sitä, mitä tavallisimpien ikkunointijärjestelmiin liittyvillä kirjastoilla (C / C++). Netscape alkoikehittämään omia GUI-komponentteja ja niihin liittyviä luokkia — kokonaisuutta se kutsui InternetFoundation Classes -nimellä. Sun huolestui tästä kehityksestä, ja aloitti yhteistyön, jonka tuloksenasyntyi JFC.

Jatkossa AWT:tä ei enää ole tarkoitus kehittää — kehitystyösuunnataan pikemminkin Swingiin.Swing koostuu yli 600 luokasta (JDK 1.6), rajapinnat mukaanlukien. JFC puolestaan koostuu Swin-gin lisäksi muista kokonaisuuksista. Swing rakentuu AWT:npäälle, joten sen ei ole tarkoitus täysinsyrjäyttää AWT:tä.

Swingin ja AWT:n pääasialliset erot ovat seuraavat.

• Swingissä onenemmän graafisia komponenttejakuin AWT:ssä. Joidenkin tahojen mukaan mo-ninkertaisesti enemmän. Swing-komponentteja on n. 40 kpl,kun AWT:ssä varsinaisesti on vainn. 15 komponenttia. Osa erosta selittyy sillä, että menuihin liittyviä luokkia ei AWT:ssä lue-ta komponentteihin — Swingissä luetaan. Itse asiassa, AWT:hen verrattuna Swingissä on vainmuutama täysin uudenlainen komponentti. Toisaalta Swingissä on useita samantapaisia kom-ponentteja, esimerkiksi liu’uttimia on useita.

Swing-komponenttien nimet alkavat ’J’-kirjaimella, esimerkiksiJButton, JFrame ja JText-Field.

• Swing-komponentit ovatominaisuuksiltaan monipuolisempia. Esimerkiksi, komponentteihinvoidaan liittää erilaisia dekoratiivisiareunustuksia(border). Lisäksi kaikkiin komponentteihinvoidaan liittäätekstivihjeja pikanäppäimiä. Yleensäkin tarkoitus on, ettäSwing-sovelluksia voisuorittaa tarpeen vaatiessa täysin ilman hiirtä.

• Suurin osa Swing-komponenteista onkeveitä. Tämä tarkoittaa, että niihin ei liity vastinoliota,vaan komponentit piirtävät itsensä jonkin raskaan komponentin taustaa vasten. Swingin kompo-nenteista vain neljä onraskaita: päätason komponenttisäiliöluokatJApplet, JWindow, JFrameja JDialog. Ks. kuva 7.1. Keveydellä tavoitellaan tehokkuutta ja Swingin komponentit ovatkintehokkaampiakuin AWT:n vastaavat.

85

Page 86: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

86 LUKU 7. JOHDATUS SWING-SOVELLUKSIIN

• Swing-komponentteihin liittyymalli. Mallin tehtävänä on esittää komponentin tilainformaatio-ta. Malli on oma luokkansa ja siihen on mahdollista päästä käsiksi. Itse asiassa monien kompo-nenttien kohdalla olennainen osa toiminnan määrittelyä onkomponentin malliolion määrittelyuudestaan perinnän avulla (esim.JTable). Monet Swing-komponentit käyttävät saman tyyp-pistä mallia, joka mahdollistaamalliolion jakamisenkahden tai useamman GUI-komponentinkesken. Mallin myötä Swing tukee ns.Model-View-Controller-suunnitteluperiaatetta (MVC)noudattaen sitä itsekin. Erityisesti, komponenteilla on myös view-olio ja itse komponetti onkontrollerin roolissa.

• Edellisten lisäksi Swing mahdollistaa komponenttiengraafisen 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ä oletusarvoi-sesti käytetään Metal-ulkoasua, mutta käytettävissä ovatmyös Motif- ja Windows-ulkoasut(edelliset ovat osa JDK:ta — useita muitakin on). Esimerkissä 7.1 näytetään, miten komponent-tien ulkoasua voidaan vaihtaa suorituksen aikana. Muutokset voidaan tehdä kokonaisvaltaisestitai komponenttikohtaisesti.

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

��������������������������������

JComponentPanel

Applet

JApplet

JWindow Frame

JFrame JDialog

Dialog

Window

Container

Component

Object

JRootPane

JPanel

JMenuBar

AbstractButton

= raskas

= Swing-komp.

= abstrakti luokka

= kevyt

Kuva 7.1: Swing-luokkien sijoittuminen AWT:n hierarkiaan.

Esimerkissä 7.1 luodaan Swing-tyyppinen kehysolio, johonsijoitetaan joukko erilaisia Swing-komponentteja. Sovellukseen liittyy kolme nappulaa, joihin kuhunkin liitetään ’LookAsettaja’-tyyppinentapahtumankäsittelijä, joka nappulan painalluksen seurauksena vaihtaa kaikkien komponenttien ul-koasua. Tapahtumankäsittelijä saa tarpeellisen informaation konstruktorin parametrin kautta: ulkoasuavoidaan vaihtaa luokanUIManager metodilla ’setLookAndFeel’ — eräs metodin käyttötavoistaonLookAndFeel-tyyppisen luokan nimen antaminen. Huomaa, miten ’actionPerformed’:n lopussa uuttaulkoasua sovelletaan kaikkiin komponentteihin. Luotava kehysolio on muodostettu perimällä luokastaJFrame, vaikka perinnän yhteydessä ei määritelläkään mitään metodeja uudestaan. Edellisestä joh-

Page 87: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

87

tuen käyttöliittymän muodostaminen tapahtuu konstruktorin rungossa, ’main’-metodissa riittää vainluoda kehysolio (ja sen seurauksena kaikki muutkin oliot) ja tapahtumankäsittelijäoliot ovat sisäluo-kan mukaisia oliota (mistä johtuen instanssimuuttuja ’alue’ on käytettävissä mahdollisen poikkeuksensyyn tulostamiseen).

Kuva 7.2: Esimerkin 7.1 ohjelma Motif-ulkoasulla.

Kuva 7.3: Esimerkin 7.1 ohjelma Metal-ulkoasulla.

Page 88: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classAlustaDemoextendsJFrame {

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 =newJPanel();paneeli.add(m1); paneeli.add(m2); paneeli.add(m3);paneeli.add(newJTextField("Tekstikenttä" ));paneeli.add(newJCheckBox("Valintalaatikko" ));paneeli.add(newJLabel("Leima" ));paneeli.add(newJRadioButton("Radionappula" ));String[] lista = { "yksi" , "kaksi" , "kolme" };paneeli.add(newJList(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(newLookAsettaja("javax.swing.plaf.metal.MetalLookAndFeel" ));m2.addActionListener(newLookAsettaja

("com.sun.java.swing.plaf.motif.MotifLookAndFeel" ));m3.addActionListener(newLookAsettaja

("com.sun.java.swing.plaf.windows.WindowsLookAndFeel " ));}

private classLookAsettajaimplementsActionListener {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

Page 89: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

7.1. SWING-KOMPONENTEISTA 89

7.1 Swing-komponenteista

Seuraavassa lyhyt esittely Swing-komponenteista (paketissajavax.swing ). Useita seuraavista tar-kastellaan yksityiskohtaisesti luvussa 8.

7.1.1 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. Taulukossa7.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 (periiFrame: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 luokatPa-

nel ja Canvas).JRootPane: Eräänlainen pääsäiliö komponenteille.JScrollPane: Säiliö, jonka reunoilla liu’uttimet. Monissakaan Swingin komponenteissa ei ole auto-

maattisesti liu’uttimia, jolloin sellaiset “saadaan” tämän avulla.JTabbedPane: Säiliö, joka toteuttaaCardLayout:n tapaisen toiminnallisuuden.JWindow: Samantapainen kuinJFrame, mutta ei otsikkoa. Itsenäinen ikkuna.

Taulukko 7.1: Swingin säiliöluokkia.

7.1.2 AWT:n peruskomponentteja vastaavat komponentit

Taulukossa 7.2 on kuvattu lyhyesti ne Swingin komponentit,joilla on suoraan vastine AWT:ssä. Kom-ponentteja tarkastellaan lähemmin luvussa 8.

JButton: Nappula, jota painamalla aiheutuuActionEvent-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 luokastaContainer!).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.

Page 90: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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.

7.1.3 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 osaksikomponenttia.

JComboBox: Monipuolinen tekstikentän ja pudotusvalikon yhdistelmä. Jos valikossa ei ole sopivaakohtaa, 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 suunnis-

sa.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ätJRootPane-luokan olion,juuriruudun (root pane) — säiliöön lisättävät komponentit tulee liittää ky-seisen olion yhteyteen (säiliöruutuun tai kerroksisuusruutuun).

JFrame: Reunat ja otsikkopalkki.JWindow: Ei osikkopalkkia eikä reunoja mutta on itsenäinen olio (eitarvitse tuekseen kehystä kuten

AWT:ssä).JApplet: KutenApplet, mutta sisältönäJRootPane.JDialog: KutenDialog, mutta sisältönäJRootPane.

Page 91: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

7.2. KOMPONENTTISÄILIÖISTÄ 91

Kuvan 7.1 perusteella kaikki edelliset luokat perivät AWT:n luokastaContainer, mistä johtuenniillä on ’add’-metodi komponenttien lisäämistä varten.Kyseistä ’add’-metodia ei kuitenkaan tulisikä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: “ker-roksisuusruudusta” (layered pane) jalasiruudusta(glass pane). Lasiruutu onComponent-luokan olioja katsojan kannalta lasiruutu sijaitsee koko komponenttisäiliön päällä. Oletusarvoisesti lasiruutu eiole “näkyvissä”, mutta näkyvissä ollessaankin se on läpinäkyvä! Lasiruutua käytetään lähinnä sii-hen, että sen avulla voidaan estää käyttäjän tapahtumien kulkeutuminen säiliön komponentteihin. Joslasiruutuun piirtää jotakin, niin se tulee koko sovelluksen päälle.

Kerroksisuusruutu koostuumenupalkista, JMenuBar-tyyppinen olio, jasäiliöruudusta(contentpane),Container-tyyppinen olio. Jos komponenttien halutaan sijaitsevan samalla tasolla, kerroksi-suusruutu “ohitetaan” ja komponentit lisätään suoraan säiliöruutuun — tämä on tavallista. Jos kom-ponentteja 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 nap-pulaa, joissa on sisältönä tekstin lisäksi tietyistä URL-osoitteista haettuja kuvia. Nappuloiden avullasäädellään, toimivatko koeikkunaan laitetut komponentitvai eivät. Toimivuudella tarkoitetaan, ettäpääsevätkö käyttäjän aiheuttamat tapahtumat vaikuttamaan kyseisiin komponentteihin. Sääteleminentapahtuu lasiruudun avulla: ’estä’-nappula tekee koeikkunan lasiruudusta “näkyvän”, jolloin siihenkiinnitetty tapahtuman käsittelijä estää hiiritapahtumien kohdistumisen säiliön komponentteihin. Huo-maa, miten ’klikkausKuluttaja’-muuttujaan luotu sisäluokan mukainen tapahtumankäsittelijä osaa ku-luttaa kolmenlaisia hiiritapahtumia. Nappulan ’salli’ merkitys on puolestaan lasiruudun muuttaminennäkymättömäksi, jolloin tapahtumat pääsevät etenemään normaalisti. Huomaa, miten nappuloihin ’es-tä’ ja ’salli’ kiinnitetyt tapahtumankäsittelijät tarvitsevat tietoa luokkamuuttujasta ’koeIkkuna’. Sovel-luksen lopettava tapahtumankäsittelijä on toteutettu myös anonyymillä sisäluokalla ja se on kiinnitettykolmanteen nappulaan. Koeikkunaan on sijoitettu kolme komponettia: värivalintatyökalu, radionappu-la ja tekstikenttä. Värivalintatyökaluun kiinnitetty tapahtumankäsittelijä muuttaa pääikkunan taustanväriä (tieto pääikkunaoliosta välitetään staattisen anonyymin sisäluokan mukaiselle tapahtumankäsit-telijälle luokkamuuttujan ’pääIkkuna’ välityksellä).

Kuva 7.4: Esimerkin 7.2 ulkoasu.

1Metodin ’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’. . .

Page 92: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classTestGlassPane {

private static final String quitS ="http://www.cs.utu.fi/icons/bomb.gif" ;private static final String preventS ="http://www.cs.utu.fi/icons/hand.up.gif" ;private static final String continueS ="http://www.cs.utu.fi/icons/continued.gif" ;private static JFrame pääIkkuna, koeIkkuna;private static MouseAdapter klikkausKuluttaja;public static void main(String[] args) {

koeIkkuna = teeKoeikkuna(); pääIkkuna =newJFrame("Pääikkuna" );ImageIcon quitIcon, stopIcon, go_onIcon;try { quitIcon = new ImageIcon(newURL(quitS)); }catch (MalformedURLException e) { quitIcon =null ; }try { stopIcon =new ImageIcon(newURL(preventS)); }catch (MalformedURLException e) { stopIcon =null ; }try { go_onIcon =new ImageIcon(newURL(continueS)); }catch (MalformedURLException e) { go_onIcon =null ; }JButton b1 =newJButton("Estä" , stopIcon);JButton b2 =newJButton("Salli" , go_onIcon);JButton b3 =newJButton(quitIcon);Container pane = pääIkkuna.getContentPane();pane.setLayout(newFlowLayout(FlowLayout.CENTER, 5, 5));pane.add(b1); pane.add(b2); pane.add(b3);b3.addActionListener(newActionListener(){

public void actionPerformed(ActionEvent e) {System.exit(0); }});

klikkausKuluttaja =newMouseAdapter(){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(newActionListener(){

public void actionPerformed(ActionEvent e) {koeIkkuna.getGlassPane().setVisible(true); }});

b2.addActionListener(newActionListener(){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);

} // mainprivate static JFrame teeKoeikkuna() {

JFrame f =new JFrame("Koeikkuna" );Container pane = f.getContentPane(); pane.setLayout(newFlowLayout());final JColorChooser cc =new JColorChooser();pane.add(cc); pane.add(newJRadioButton("Radionappula" ));pane.add(newJTextField("Alustettu tekstikenttä" ));cc.getSelectionModel().addChangeListener(newChangeListener() {

public void stateChanged(ChangeEvent e) {pääIkkuna.getContentPane().setBackground(cc.getColor()); }});

return f;} // teeKoeikkuna

} // TestGlassPane

Page 93: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

7.2. KOMPONENTTISÄILIÖISTÄ 93

MenuBar

LayeredPane

RootPane

Content pane(Container)

Glass pane(Component)

Kuva 7.5: Swingin komponenttisäiliöiden ruuturakenne.

Esimerkki 7.3 havainnollistaa, miten komponentteja voi sijoitella eri kerroksiin. Samalla havain-nollistetaan komponenttien läpinäkyvyyttä ja komponenttien sijoittelua ilman ikkunamanageria. Ikku-namanagerin voi poistaa asettamalla sen ’setLayout’-metodilla null:ksi. Tuolloin komponentit pitää si-joitella 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ää kerroksiajärjestyksessä alimmasta ylimpään.

Kuhunkin kerrokseen luodaan esimerkissä yksi komponentti. Luodut komponentit (nappulat) ovatlä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äänJLayeredPane-tyyppiseen säiliöön.Jokaiseen komponenttiin liitetyn tapahtumankäsittelijän avulla voi havainnoida, milloin hiiri on kom-ponentin alueella — tästä voi päätellä, milloin komponentti on toisen päällä!

Page 94: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classLayersDemo {

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 =newJFrame("LayersDemo" );frame.setSize(400,300);Container cp = frame.getContentPane();JLayeredPane jcp = frame.getLayeredPane();cp.setLayout(null );JButton[] nappulat =newJButton[5];Border etcBorder = BorderFactory.createEtchedBorder();for (int i=0; i<5; i++) {

nappulat[i] =newJButton("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 =newJTextField("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 classNappulaKuuntelijaextendsMouseMotionAdapter {private JTextField tulos;NappulaKuuntelija(JTextField tf) { tulos = tf; }public void mouseMoved(MouseEvent e) {

tulos.setText("(" +e.getX()+"," +e.getY()+")" );}

} // class NappulaKuuntelija} // class LayersDemo

Page 95: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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-komponenttien-kin 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ä me-todit käsittelijöiden kiinnittämiseksi komponenttiin (ja poistamiseksi). Swing-komponenttien toimin-ta 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, muutamissakohdin olisi voitu käyttää AWT:n tapahtu-maa, mutta Swingiin on esitelty uusi tapahtuma sitä varten.Tämä on sikäli harmillista, että pitää hyvintarkkaan muistaa, mitä tapahtumia mikäkin komponentti käsittelee (ja millainen tapahtuma minkäkinmatalan tason tapahtuman seurauksena syntyy).

Swingin tapahtumat ovat pääsääntöisesti paketissajavax.swing.event . Itse asiassa Swingmäärittelee kaksi uutta matalan tason tapahtumaa:KeyEvent:n aliluokanMenuKeyEvent ja luokanMouseEvent uuden aliluokanMenudragEvent. Muut Swingin tapahtumat ovat semanttisia tapah-tumia, jotka perivät luokastajava.util .EventObject. Poikkeuksen edelliseen muodostavat luokatAncestorEvent ja InternalFrameEvent, jotka perivät luokastajava.awt .AWTEvent. Taulukossa7.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-komponenttienkokonaisuus, jonka on tarkoitus syrjäyttää AWT GUI-sovellusten tekemisessä. Swing-komponentitnoudattavat MVC-suunnittelumallia ja toisaalta lähes kaikki Swing-komponentit ovat keveitä. Swin-gin komponenttisäiliöt (JFrame, JApplet, JWindow ja JDialog) toimivat varsin eri tavalla kuin

Page 96: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

96 LUKU 7. JOHDATUS SWING-SOVELLUKSIIN

AncestorEvent: Johonkin komponentin sisältävään säiliöön on tapahtunutjokin 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 liit-

tyviä 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 samaanSwing-pohjaiseen komponenttisäiliöön. Tuolloin pitää kuitenkin muistaa keveiden ja raskaiden kom-ponenttien ero:kevyet vain piirtävät itsensä säiliön piirtopinnalle, kunraskaat komponentit esitetäänikkunointijärjestelmän vastionolioilla. Vastinoliot piirtyvät aina säiliön taustan päälle!

Page 97: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

Luku 8

Swing-komponentit

Tässä luvussa tehdään melko pintapuolinen katsaus moniin Swingin komponentteihin. AWT:n koh-dalla komponentteja tarkasteltiin perusteellisesti. Nyttarkoituksena on todeta, millaisia ominaisuuksiaSwingin vastaavilla komponenteilla on — missä mielessä ne ovat parempia / erilaisia. Osalla Swinginkomponentteja ei ole vastinetta AWT:ssä (esim.JTable ja JTree) — näiden kohdalla ominaisuuk-sia tarkastellaan perusteellisemmin. Osa uusista Swing-komponenteista ohitetaan (mm.JViewport jaJSpinner).

Tarkastelu aloitetaan leimasta luokanJLabel kautta luvussa 8.1. Tässä yhteydessä sivutaan luok-kia Icon ja ImageIcon, koska Swingissä komponentit voivat tekstin lisäksi sisältää kuvia. Seuraavak-si luvussa 8.2 tarkastellaan Swingin tekstikenttä ja -editorikomponentteja:JTextComponent, JText-Field, JPasswordField, JTextArea, JEditorPane, JTextPane. Swingin nappuloita ja niihin rinnas-tettavia komponentteja käsitellään luvussa 8.3 luokkienAbstractButton, JButton, JToggleButton,JCheckBox, JRadioButton ja ButtonGroup kautta. Liu’uttimia tarkastellaan luokkienJScrollBar,JSlider ja JScrollPane kannalta luvussa 8.4. Listoja käsitellään vastaavasti luvussa 8.5 luokkiaJListja JComboBox tarkastelemalla. Swingin toiminnan tukena on myös suurehko joukko muita seka-laisia GUI-komponentteja ja “apuluokkia”. Näistä luokkiaJProgressBar, JToolTip, JSeparator,JColorChooser ja JFileChooser tarkastellaan luvussa 8.6. Layout-asiaa asiaa käsitellään hiemanluvussa 8.7 luokkienJTabbedPane, JSplitPane ja BoxLayout avulla. AWT:hen verrattuna menui-den 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äänluvussa 8.10 yhtä Swingin monipuolisimmista GUI-komponenteista, nimittäin luokkaaJTable. Vas-taavalla tavalla uusi ja monipuolinen komponenttiJTree on luvun 8.11 aiheena. Lopuksi pienimuo-toinen yhteenveto tehdään luvussa 8.12.

8.1 LeimaJLabel

Leima on yksinkertainen peruskomponentti, joka perii luokastaJComponent. Sen ominaisuuksiaovat mm.tekstisisältö, ikonikuva(Icon) ja vaaka- sekä pystysuuntaisen kohdistuksen arvot. Luokankonstruktorilla 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 rajapinnanIcon avulla — rajapinnan toteuttaa luokkaImageIcon, jonka avullavoi luoda ikonin esimerkiksi GIF- tai JPEG-kuvasta.

Kuvan liittäminen komponenttiin on yleinen Swingin mukanatullut laajennos — ideana on, ettäjos AWT:n kohdalla oli jossakin komponentissa käyttäjällenäkyvä teksti, niin lähes kaikkiin sellaisiin

97

Page 98: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

98 LUKU 8. SWING-KOMPONENTIT

komponentteihin on mahdollista asettaa tekstin sijaan/rinnalle kuva. Toinen yleinen Swingin kuviinliittyvä laajennos on, että komponenttiin on mahdollista asettaa useita kuvia erilaisia komponenttiinliittyviä tilanteita varten. Esimerkiksi,JLabel:ssa on mahdollista asettaa toinen ikoni komponentin“disabled”-tilaa varten.

Luonnollisesti edellä kuvatuille leiman ominaisuuksilleon vastaavat get- (havainnointi) ja set-metodit (muuttaminen). Kaikkiin Swingin GUI-komponentteihin liittyy mahdollisuus määrätä kom-ponentti, jonka avulla tuotetaan graafinen ulkoasu (ns. rederöijä; Look&Feel:n asettaminen). Metodil-la ’setUI’ voidaan asettaa uusi ulkoasun tuottava olio (tyyppiä LabelUI). Vastaavasti ’getUI’:llä voihavainnoida asiaa. Metodilla ’updateUI’ voi tuottaa päivitetyn ulkoasun.

8.2 Tekstikentät

Leimaan ei liity MVC-ajattelun mukaista mallioliota, mutta useimpiin Swingin komponentteihin sel-lainen liittyy. Erityisesti kaikilla tekstikomponenteilla on malliolio! Se on kaikkien erilaisten teksti-komponenttien yhteydessä samaa tyyppiä:Document (rajapinta). Huomaa, että erillisen malliolionolemassaolo tarkoittaa myös sitä, että yksittäinenmalliolio on mahdollista jakaa usean komponentinkesken! Malliolio sisältää komponentin “datan”, joten jakamistilanteessa sama data on usean kom-ponentin sisältönä ja yhteen tehty muutos heijastuu välittömästi toisiin samaa mallioliota käyttäviinGUI-komponentteihin. Malliolion voi asettaa metodilla ’setDocument’ ja sitä voi havainnoida meto-dilla ’getDocument’.

Useimmat Swingin tekstikomponentit ovat hyvin samanlaisia kuin AWT:n vastaavat. Muutoksetovat pääasiallisesti kirjattu kaikkien tekstikomponenttien yliluokaksi määritettyyn luokkaanjavax.swing.text .JTextComponent, ja merkittävin muutos on malliolion mukaantulo. Malliolion li-säksi Look&Feel:n asettaminen on samoin kuin leiman kohdallakin ja lisäksi malliolion kautta teks-tikomponentteihin on mahdollista rakentaa undo/redo -toiminnot (mutta niitä ei oletusarvoisesti ole).Kursorin kohdistuksiin on mahdollista reagoida tapahtumankäsittelijöillä. Seuraavassa lyhyt kuvausmuista Swingin tekstikomponenteista:

⋆ JTextField — On hyvin samanlainenTextField:n kanssa. Yksirivinen tekstikomponentti, jokasisältää vain tekstiä (ei kuvia). ’Enter’:n painallus komponentin yhteydessä aiheuttaa tapahtu-manActionEvent.

⋆ JPasswordField — Tämä luokka on perittyJTextField:stä ja sen erikoisuus edelliseen verrat-tuna on, että kaiutus on “salattu”.

⋆ JTextArea — Tämä komponentti toimii kutenTextArea, 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ä esi-merkissä 8.1 olevan ohjelman ’JTextDemo’ ulkoasu.

Esimerkin yläosassa on 4 tekstikenttää. Ensimmäiseen kirjoitetun URL:n perusteella haetaan WWW-sivu kuvan alaosassa olevanJEditorPane-komponentin sisällöksi — ensimmäiseen tekstikenttään onlaitettu tapahtumankäsittelijä, jonka laukeaminen asettaa aina uuden WWW-sivun voimaan. Huomaa,

Page 99: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.2. TEKSTIKENTÄT 99

mitenJEditorPane-tyyppinen komponentti pystyy esittämään erilaisia graafisia komponentteja, joi-ta WWW-sivuun on voitu upottaa. Nimensä mukaanJEditorPane on editori, jolla voi malliolioksiladatun dokumentin sisältöä muuttaa — kokeile lisäämistä ja poistamista.

Toinen ja neljäs tekstikenttä on “liitetty” toisiinsa siten, että ne jakavat keskenään yhteisen mallio-lion. Riippumatta siitä, kumpaan komponenttiin editointi(kirjoittaminen) kohdistuu, niin molempiensisältönä on sama merkkijono. Neljäs kenttä onJPasswordField-tyyppinen, joten sen sisältö ei kai-utu selväkielisenä — sen sijaan toinen tekstikenttä on “normaalia”JTextField-tyyppiä. Kolmas teks-tikenttä on myös salasanakenttä. Neljän tekstikentän muodostaman kokonaisuuden jaJEditorPane-tyyppisen komponentin välissä on vieläJTextArea-tyyppinen komponentti.

Kuva 8.1: JTextDemo suorituksessa.

Page 100: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classJTextDemo {

// 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 =newJTextField("http://" );JTextField tf2 =newJTextField(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 =newJTextArea(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 =newJLabel("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 =newJEditorPane();JScrollPane sp =newJScrollPane(ep);sp.setPreferredSize(new Dimension(550,400));c.add(sp);f.setSize(700,800); f.setVisible(true);f.addWindowListener(new IkkunanSulkija());

} // main} // class JTextDemo

Page 101: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.2. TEKSTIKENTÄT 101

Esimerkissä 8.2 havainnollistetaan, miten luokastaJTextField voidaan johtaa luokka, johon eimitenkään voi kirjoittaa pieniä kirjaimia — tai oikeammin,voi kirjoittaa, mutta ne muuttuvat isoiksikirjaimiksi. Esimerkissä 4.1 oli samantapainen tilanne, joka ratkaistiin matalantason tapahtumia suo-dattamalla — nyt ratkaisuna on malliluokan (suoraviivainen) uudelleenmäärittely. Ainoastaan yksimalliluokan metodi pitää määritellä uudelleen. Tästä johtuen uuden tekstikomponentin ’UpperCase-Field’ kohdalla pitää määritellä uudelleenJTextComponent:sta peritty metodi ’createDefaultModel’.Huomaa vielä, miten uutta tekstikomponenttia käyttävässäappletissa määritellään dekoratiiviset ko-ristukset 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 classUpperCaseDemoextendsJApplet {

public void init() {Container c = getContentPane();JTextField tf1 =newJTextField(20);tf1.setBorder(BorderFactory.createLineBorder(Color.green));c.add(tf1, BorderLayout.NORTH);JTextField tf2 =newUpperCaseField(20);tf2.setBorder(BorderFactory.createLineBorder(Color.red));c.add(tf2, BorderLayout.SOUTH);setSize(300,300);

} // init} // UpperCaseDemo

// JDK 1.3:n dokumentaatiostaclassUpperCaseFieldextendsJTextField {

public UpperCaseField(int cols) { super(cols); }

protected Document createDefaultModel() {return new UpperCaseDocument(); }

static classUpperCaseDocumentextendsPlainDocument {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,newString(upper), a);

}} // class UpperCaseDocument

} // class UpperCaseField

Page 102: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

102 LUKU 8. SWING-KOMPONENTIT

Kuva 8.2: UpperCaseDemo suorituksessa.

8.3 Swingin nappulat

Swing-nappuloiden yliluokkaAbstractButton määrittelee niiden yhteiset ominaisuudet. Kyseiselläluokalla on yli 70 metodia! AWT-nappuloiden tapaan Swing-nappuloiden merkitys on, että niihin voiliittää tapahtumankäsittelijän (tai useita), joka laukeaa nappulaa klikattaessa (hiirellä). Tekstikompo-nenttien tapaan nappuloilla onmalliolio ja reunaviivat. Toisaalta leimojen tapaan sisältönä ontekstiäja/tai ikoni. Nappuloihin liittyy enemmän “tilanteita” kuin leimoihin. Jokaista “tilaa” kohti voidaanasettaa erityinen ikoni: erityiset “estetty”-, “valittu”- sekä “RollOver”-ikonit (hiiri kulkee nappulanyli), . . . . Lisäksi on mahdollista asettaa nappulaan merkki, jonka painaminen näppäimistöltä aiheuttaanappulan “klikkauksen”. Osa nappuloista on sellaisia, että niihin liittyy on/off-tila ja klikkaus vaihtaanappulan tilaa tässä mielessä. Lisäksi erilaisiin nappuloihin liittyy erilaisia tapahtumankäsittelijöitä.

AbstractButton

JButton

JToggleButton

JCheckBox

JRadioButton

JMenuItem

Kuva 8.3: Swingin nappulaluokkia.

Swingin nappuloiden välistä perimyshierarkiaa on havainnollistettu kuvassa 8.3. Hierarkia on piir-retty hieman epätavallisesti niin, että hierarkian juuri on vasemmassa reunassa (AbstractButton).Hierarkia ei ole täydellinen – esimerkiksi luokallaJButton on useita aliluokkia. Nappuloiden ominai-suuksia on lyhyesti selitetty seuraavassa:

⋄ JButton: Perusnappula, johon ei liity on/off-tilaa. Reagoi vainActionEvent-tapahtumiin (nap-pulan klikkaus).

⋄ JToggleButton: On/off-tyyppinen nappula: pohjassa tai ylhäällä. Painaminen tuottaa sekäActionEvent-ettäItemEvent-tapahtumat. Yleensä tämän luokan instansseja ei luoda (vaan sen aliluokkien).

⋄ JCheckBox: AWT:stä tuttu valintalaatikko. Klikkaus tuottaaItemEvent tapahtuman.

⋄ JRadioButton: Esittää radionappulaa, joka voi kuulua johonkin radionappularyhmään. Ryh-mien luokka onButtonGroup — sillä on sama rooli kuin AWT:ssä. Swingin tapauksessa nap-

Page 103: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.3. SWINGIN NAPPULAT 103

pulat lisätään ryhmään! Tapahtumat:ItemEvent.

Edellisen tapaiset nappulat voidaan laittaa myös osaksi menua (luokanJMenuItem alta löytyy lisäänappuloita).

Kuvassa 8.4 on esitetty sivulla 104 olevan esimerkin 8.3 ohjelman ’NappulaDemo’ ulkoasu. Huo-maa, miten nappuloihin voi liittää ikonin ja/tai tekstiä. Ohjelmassa on kolme nappulaa ja yksi teksti-kenttä. Nappuloita painamalla tapahtuu monenlaista, mutta erityisesti kahden ensimmäisen klikkauskasvattaa tekstikentässä esitettävää lukuarvoa. Ensimmäisen klikkaus kasvattaa kahdella ja toisenklikkaus 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 lukuarvoa2 + 1:llä. Kolmas nappula “sotkee” tilannetta niin, että sen klikkaaminen muuttaa toisen nappulantilan ’estyneeksi’ / ’ei-estyneeksi’. Kun ohjelman ’NappulaDemo’ koodia katsoo, niin huomaa, et-tä 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äänniiden perusteella ikonikuvat. Edelleen kuvasta voi huomata, että nappuloihin on liitetty pikanäppäintoiminto ja vihjetekstit. Myös tekstikomponenttiin on liitetty vihjeteksti. Jos komponenttiin liittyy se-kä vihjeteksti että pikanäppäintoiminto, niin pikanäppäimen koodi tulee automaattisesti osaksi vihje-tekstiä (kuten kuvasta 8.4 on havaittavissa).

Kuva 8.4: NappulaDemo suorituksessa.

Page 104: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classNappulaDemo {

private static String burst ="http://www2.cs.utu.fi/icons/burst.gif" ;private static String at_work ="http://www2.cs.utu.fi/icons/at_work.gif" ;private static JButton b1, b2;private static JTextField tf =newJTextField("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 =newJButton(i1);b2 =newJButton("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

Page 105: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.4. LIU’UTTIMET 105

8.4 Liu’uttimet

AWT:ssä muutamiin komponentteihin tuli reunalle liu’uttimet “automaattisesti”, jos esitettävä kom-ponentti oli suurempi kuin sille varattu tila. Swingin kohdalla tästä on pyritty eroon ja tarkoitus on,että käyttäjä lisää “ylisuuret” komponentitJScrollPane-tyyppiseen olioon. Ideana on luoda näky-mä komponenttiin “ikkunan” kautta, kuten AWT:nScrollPane:lla. LuokanJScrollPane avulla voimäärätä tuleeko vaaka- ja/tai pystysuuntainen liu’utin alueen reunalle.

Arvon valitsemista (kokonaislukuväliltä) varten Swingissä on myös kaksi luokkaa:JScrollBarja JSlider. NäistäJScrollBar toimii kuten ScrollBar ja muutenkin niiden ulkoasut ovat samankal-taiset. LuokanJScrollBar-alkioilla on kuitenkin malliolio, joka on tyyppiäBoundedRangeModel.Jos liu’uttimen arvoa muutetaan (AWT:n vastaavan komponentin tapaan), syntyyAdjustmentEvent-tyyppinen tapahtuma.

LuokanJSlider mukaiset liu’uttimet ovat hieman monipuolisempia ja näyttävämpiä, mutta niilläon sama toimintaidea kuinJScrollBar:llakin. Säädettävistä ominaisuuksista hyödyllisin lienee eri-laisten väliasteikkojen asettaminen liu’uttimen yhteyteen. Tähänkin luokkaan liittyy malliolio, jokaon tyyppiäBoundedRangeModel. JSlider- ja JScrollBar-tyyppisten olioiden kesken on siis mah-dollista jakaa malliolio. Jostain (kummasta) syystäJSlider tuottaaChangeEvent-tyyppisiä tapahtu-mia.

Kuva 8.5: SlideDemo suorituksessa.

Kuvassa 8.5 on esitetty ohjelman ’SlideDemo’ ulkoasu (ohjelmakoodi on esimerkissä 8.4). Esi-merkissä on neljä liu’utinta ja niihin kuhunkin on liitettytapahtumankäsittelijä, joka raportoi liu’uttimensen hetkisen arvon vastaavassa tekstikentässä. Esimerkkiin liittyvä erikoisuus on, että toinen ja neljäsliu’utin jakavat saman malliolion keskenään — kokeile esimerkkiä!

Page 106: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classSlideDemo {

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 =newJTextField(10); a2 =newJTextField(10);b1 =newJTextField(10); b2 =newJTextField(10);sb1 =newJScrollBar(JScrollBar.HORIZONTAL,10,8,0,100);sb2 =newJScrollBar(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 =newJSlider(0, 100, 10);js2 =newJSlider(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

Page 107: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.5. LISTAT 107

8.5 Listat

Swingissä valintalistoja voidaan toteuttaa luokillaJList ja JComboBox. Luokka JList on moniva-lintalista kuten vastaava luokkaList AWT:ssä. Itse asiassa Swingissä ei ole suoraan AWT:n luokkaaChoice vastaavaa luokkaa. LuokanJComboBox merkitys on hyvin erilainen.

LuokanJList olioiden ominaisuuksia ovat mm. seuraavat: joukkotekstialkioita, näkyvien luku-määrä ja alkukohta, reunallaei ole liu’uttimia , valitut alkiot, malliolio ja valintatila. Valintatilojaon useita. Voidaan valita yksi alkio, jokin yhtenäinen välitai mielivaltaisesti poimia joitakin alkioita.Swingin yleisen periaatteen mukaan liu’uttimet täytyy itse lisätä laittamallaJList tyyppinen kom-ponenttiJScrollPane:een!JList-tyyppiset oliot tuottavatListSelectionEvent-tapahtumia, joita voikäsitelläListSelectionListener:stä periytetyillä tapahtumankäsittelijöillä.

LuokanJList alkioiden ei itse asiassa tarvitse olla tekstejä. Kuvien esittäminen listassa on myösmahdollista, mutta tuolloin pitää tehdä uusi listamalli jaesitysmoduuli (renderöinti).

LuokkaJComboBox on tavallinen pudotusvalikko (vrt. luokkaChoice) täydennettynä mahdol-lisuudella kirjoittaa valinta vapaasti itse käyttäjän toimesta. Eli,JComboBox on tavallaan myös teks-tikenttä. Pudotusvalikon alkiot ovat oletusarvoisesti tekstejä, mutta kuvia on myös mahdollista esittää— tilanne on tältä osin sama kuin luokanJList kohdallakin. Valittaessa jokin pudotusvalikon alkiosyntyyItemEvent-tyyppinen tapahtuma. LuokatJList ja JComboBox käyttävät eri tyyppistä mallio-liota.

Kuva 8.6: JListatDemo suorituksessa.

Kuva 8.6 esittää ohjelman ’JListatDemo’ (esimerkki 8.5) ulkoasua. ’JListatDemo’ havainnollis-taa Swingin listoja: molempia listatyyppejä on kaksi. Kuhunkin listaan on kiinnitetty tapahtumankä-sittelijä. Vasemmassa reunassa olevaanJList-tyyppiseen komponenttiin ja keskellä ylhäällä olevaanJComboBox-tyyppiseen komponenttiin kiinnitetyt tapahtumankäsittelijät raportoivat, ylhäällä ole-vaan tekstikenttään, tehdyistä valinnoista. Vastaavastitoiset kaksi listakomponettia raportoivat ala-reunassa olevaan tekstikomponenttiin. Huomaa, miten ’setEditable’-metodia kutsumalla ohjelmassaasetetaan alempiJComboBox-komponentti sellaiseksi, että käyttäjä voi itse kirjoittaa valinnan, joslistassa sellaista ei ole. Reunoilla olevien listojen valintatilat ovat erilaiset — kokeile usean arvonvalitsemista oikeanpuoleisella listalla. Huomaa myös, mikä vaikutus on sillä, että oikeanpuoleiseenlistaan ei ole liitetty liu’utinta (alkioiden ’kuusi’ ja ’seitsemän’ valinta on nyt vaikeaa . . . ).

Page 108: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classJListatDemo {

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 =newJTextField(40);JTextField tf2 =newJTextField(40);list1.addListSelectionListener(newValitsija(tf1));list2.addListSelectionListener(newValitsija(tf2));c.add(list1, BorderLayout.EAST); c.add(newJScrollPane(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(newGridLayout(0,1));p.add(cb1); p.add(cb2); c.add(p, BorderLayout.CENTER);cb1.addItemListener(newComboValitsija(tf1));cb2.addItemListener(newComboValitsija(tf2));f.pack(); f.setLocation(200,100); f.setVisible(true);f.addWindowListener(new IkkunanSulkija());

} // main

static classValitsija implementsListSelectionListener {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 Valitsijastatic classComboValitsijaimplementsItemListener {

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

Page 109: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.6. APULUOKKIA 109

8.6 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 luokkaJProgressBar on “kehitystilan-netta” kuvaava palkki — tyyliin 55% ratkaistu. Palkki voi olla vaaka- tai pystysuuntainen, ja siihen voiliittyä tekstiä (ks. kuva 8.7) tai olla liittymättä. TavallaanJProgressBar on hyvin samanlainen kuinJSlider ja JScrollBar, vaikka siihen ei liitykään käyttäjän mahdollisuutta siirtää palkin “kehitystilan-netta”. Samanlaisuudesta johtuenJProgressBar:lla on samanlainen malliolio kuin liu’uttimilla.

LuokkaJToolTip edustaa työkaluvihjetekstiä, jonka voi liittää mihin tahansa Swing-komponent-tiin. Tätä luokkaa harvemmin kuitenkaan tarvitaan, sillä komponentteihin liittyy myös metodi ’set-ToolTipText’ (perittyJComponent:sta), jolla voi suoraan asettaa jonkin merkkijonon komponenttiinliittyväksi vihjetekstiksi. Jos komponenttiin on liitetty pikanäppäin, se näkyy automaattisesti myösvihjetekstissä.

LuokanJSeparator mukaiset oliot toimivat pysty- tai vaakasuuntaisina erotinviivoina. Idea liittyylähinnä sijoittelumanageriinFlowLayout, jossa voidaan vaakasuuntaisen eroitinviivan avulla pakottaa“rivinvaihto” (ks. kuva 8.7).

AWT:n tapaan Swingissä on työkalu tiedoston nimen valitsemiseen: luokkaJFileChooser. Se onhyvin samanlainen komponentti kuin luokkaFileChooser. Edellisen lisäksi Swingiin liittyy värinva-lintatyökalu: luokkaJColorChooser. Esimerkissä 8.7 on ohjelma ’VariDemo’ (ulkoasu kuvassa 8.8),jossa nappulan painaminen aktivoi värinvalintatyökalun dialogi-ikkunana. Huomaa, miten dialoginkutsuminen palauttaa tuloksenaan valitun värin.

Kuva 8.7: ProgressDemo suorituksessa.

Ohjelma ’ProgressDemo’ esimerkissä 8.6 ja kuvassa 8.7 havainnollistaa eroitinviivoja (yksi pys-tysuuntainen ja kaksi vaakasuuntaista; eroittimien koko on määrätty niiden konstruktorin kutsun yh-teydessä) jaJProgressBar:a. Ohjelmassa on kaksi palkkia, joista vain toisessa on tilaa kuvaava tekstimukana (huomaa, miten se asetetaan ohjelmassa ’setStringPainted’-metodilla). Nappulaan on liitet-ty tapahtumankäsittelijä, jolla ensimmäisen palkin arvoavoi kasvattaa (myös pikanäppäintoiminnol-la). Toiseen palkkiin on liitetty tapahtumankäsittelijä,joka reagoi palkin arvon muutoksiin ja raportoine alaosassa olevaan tekstikenttään. Huomaa myös, miten jälkimmäistäJProgressBar:a kasvatetaanGUI:n muodostavassa säikeessä — se on selvä virheellisyys:kasvattaminen tulisi tehdä erillisessäsäikeessä.

Page 110: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classProgressDemoextendsJFrame {

public static void main(String[] args) {new ProgressDemo(); }

private JProgressBar pb1;private JTextField tf =newJTextField(10);

public ProgressDemo() {super("JProgressBar etc demo" );setSize(500,500);Container c = getContentPane();c.setLayout(new FlowLayout());pb1 =newJProgressBar();JButton b =newJButton("Lisää" );b.setMnemonic(KeyEvent.VK_L);b.addActionListener(new ActionListener(){

public void actionPerformed(ActionEvent e){pb1.setValue(pb1.getValue()+10);

}});JProgressBar pb2 =newJProgressBar();pb2.setStringPainted(true);pb2.addChangeListener(new ChangeListener(){

public void stateChanged(ChangeEvent e){JProgressBar pb = (JProgressBar)e.getSource();tf.setText("Arvo: " + pb.getValue());

}});

JSeparator sep1 =newJSeparator(JSeparator.VERTICAL);JSeparator sep2 =newJSeparator();JSeparator sep3 =newJSeparator();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

Page 111: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classVariDemoextendsJApplet {

private JPanel panel =newJPanel();public void init() {

JButton b =newJButton("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

Page 112: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

112 LUKU 8. SWING-KOMPONENTIT

8.7 Layout-asiaa

Swingiin liittyy useita uusia sijoittelumanagereita. Näistä mainittakoonBoxLayout ja OverlayLay-out. Luokka BoxLayout esittää sijoittelumanageria, joka järjestää komponentitvaakariviin tai pys-tyriviin. OverlayLayout on sijoittelumanageri, joka sallii komponenttien menemisen toistensa pääl-le (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 tietyn-lainen välimuoto kahden ääripään välillä.

Esimerkki 8.8 LuokanJTabbedPane soveltamista.

import java.awt.∗;import javax.swing.∗;import java.net.∗;public classTabDemo {

private static String forward ="http://www2.cs.utu.fi/icons/forward.gif" ;private static String mail2 ="http://www2.cs.utu.fi/icons/mail2.gif" ;private static String redball ="http://www2.cs.utu.fi/icons/redball.gif" ;private static String yellowball ="http://www2.cs.utu.fi/icons/yellowball.gif" ;private static String greenball ="http://www2.cs.utu.fi/icons/greenball.gif" ;

public static void main(String[] q)throws Exception {JFrame f =new JFrame("TabbedPane-demo" );JTabbedPane tp =new JTabbedPane();JPanel p1, p2, p3;p1 =newJPanel(); p2 =newJPanel(); p3 =newJPanel();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 luokatJTabbedPane jaJSplitPane. JTab-bedPane on komponettisäiliö, johon yleensä laitetaan komponentteja sisältäviä säiliöitä “korttipak-kamaisesti”. Vain yksi “korteista” on kerrallaan esillä jakuhunkin “korttiin” liittyy kortistomainenvalitsin (ks. kuva 8.9). Esimerkin 8.8 ohjelma ’TabDemo’ vain luo kolme “korttia” laittaen niihinkuhunkin yhden GUI-komponentin. Kuvassa vain “viimeinen”kortti on esillä.

Toinen erikoisuus on luokkaJSplitPane, joka on erikoinen kahden komponentin säiliö, jossa

Page 113: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.7. LAYOUT-ASIAA 113

komponentit on sijoitettu joko päällekkäin tai vierekkäin, kiinni toisiinsa. Toisinaan tätä komponettiakäytetään niin, että toisella ohjataan toista. Joustavasti toistensa suhteen venyviä rakenteita saa “lii-maamalla” tällä komponentilla komponentteja toistensa suhteen (vaaka- tai pystysuuntaan). Esimer-kissä 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 luokanJTabbedPane soveltamisesta.

import javax.swing. ∗;import java.net. ∗;import java.awt. ∗;public classJSplitPaneDemo {

public static void main(String[] q)throws Exception {JFrame f =new JFrame("JSplitPane esimerkki" );Container c = f.getContentPane();String dt ="http://www2.cs.utu.fi/datatahti/" ;JEditorPane ep1 =newJEditorPane(new URL(dt));JScrollPane sp1 =newJScrollPane(ep1);sp1.setMinimumSize(new Dimension(300,400));JTextPane ep2 =new JTextPane();ep2.setContentType(ep1.getContentType());ep2.setDocument(ep1.getDocument());// Sama kohde!JScrollPane sp2 =newJScrollPane(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

Page 114: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

114 LUKU 8. SWING-KOMPONENTIT

8.8 Menupalkki

Menupalkki on Swingissä hyvin samanlainen kuin AWT:ssäkin. Erona on lähinnä se, että Swinginvalikkokomponentit ovat selkeämmin GUI-komponentteja kun AWT:ssä ne olivat pikemminkin eri-koiskomponentteja. Toinen ero on, että Swingissä valikkoon voidaan liittää tapahtumankäsittelijä, jo-ka reagoi valikon “avautumiseen” tai “sulkeutumiseen” (jne). Tätä varten on esitelty tapahtumankä-sittelijä MenuListener. Tällaisten valikko-operaatioiden tarkoituksena on tehdä valikoista dynaami-sempia: voidaan esimerkiksi sovelluksen tilan perusteella laittaa jotkin valikon kohdista estetyiksi taiääritapauksena sovelluksen tilan perusteella voidaan koko valikon sisältö generoida aina avautumista-pahtuman yhteydessä uudestaan.

LuokkaJMenu on valikko, joka sisältää valikkoalkioita tyyppiäJMenuItem ja eroittiminaJSeparator-viivoja sekä luonnollisesti epäsuoraan (JMenuItem:n kautta) alivalikoita.JMenuItem:n vaihtoehdotovattekstialkio ja/tai ikoni, JMenu (alivalikko), JCheckBoxMenuItem jaJRadioButtonMenuItem.Atomiset valikkoalkiot (teksti ja/tai ikoni) ovat nappuloita!

Valikon alkion valinta tuottaa joko tapahtumanActionEvent tai ItemEvent. ActionEvent-tapahtumasyntyy tavallisen valikkoalkion kohdalla sekä rastitettaessaJCheckBoxMenuItem-alkio. ItemEvent-tapahtuma liittyy puolestaanJRadioButtonMenuItem-alkioon. Valikkoalkioihin voidaan liittää pika-valintoja sekä vihjeitä.

Valikoita voidaan liittää menupalkkiinJMenuBar. AWT:stä poiketen tällainen komponetti voi-daan 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ää. So-velluksella on kolme valikkoa — kaksi tavallista ja yksi help-valikko. Ensimmäisen valikon alivali-kossa ’Lisää’ (ei käy kuvassa) on erityinen tapahtumankäsittelijä (tyyppiä ’AvausKuuntelija’), jokavalikon avautumisen yhteydessä arpoo jokaisen alivalikonkomponentin 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äri-palloon (oikeammin valikon kohtaan) on kiinnitetty tapahtumankäsittelijä (metodin ’teeVäriValikko’keskivaiheilla), joka reagoi valintaan vaihtamalla kehyksen taustavärin.

Toista valikkoa on havainnollistettu kuvassa 8.10. Siihenei ole laitettu tapahtumankäsittelijöitä,vaan ideana on pikemminkin ollut esitellä erilaisia valikkokomponentteja ja pikanäppäinten liittämistävalikon alkioihin. Huomaa, miten tavallinen radionappulaja valikossa olevat radionappulat kuuluvat

Page 115: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.8. MENUPALKKI 115

samaan ryhmään.

Esimerkkiin liittyy vielä help-valikko, jonka sisältö ei ole mitenkään erityinen. Hassua tämänvalikon kohdalla on, että sitä ei edes JDK 1.5:n yhteydessä voi lisätä ’setHelpMenu’-metodilla, vaikkaniin voisi kuvitella, sillä kyseisen metodin toiminnallisuutta ei ole oikealla tavalla toteutettu — kokeileasiaa! Esimerkissä se on lisätty menupalkkiin toisin — sillä seurauksella, että valikolla ei ole nytnimeä, eikä se siten näy edes kuvassa 8.10, vaikka se onkin välittömästi kahden edellisen jälkeen jasen 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 classJMenuDemo {

private static ImageIcon redBall, greenBall, yellowBall, blueBall;private static ButtonGroup ryhmä =newButtonGroup();

private static void luoVäriPallot()throws Exception {String base ="http://www2.cs.utu.fi/icons/" ;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 =newJRadioButton("Värivalinta" );ryhmä.add(r);c.add(r);f.setJMenuBar(palkki); f.setSize(500,300); f.setVisible(true);

} // main

Page 116: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

116 LUKU 8. SWING-KOMPONENTIT

Esimerkki 8.10. 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 classAvausKuuntelijaimplementsMenuListener {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 classMenuKuuntelijaimplementsActionListener {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

Page 117: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.8. MENUPALKKI 117

8.8.1 Luokka JToolBar

AWT:stä poiketen Swingin yhteydessä päätason ikkunakomponentteihin voidaan liittää erillinen työ-kalupalkki: luokanJToolBar olio. Sillä ei ole menupalkin tapaan kiinteää paikkaa, vaanse voi kiin-nittyä vaakareunaan tai pystyreunaan. Käyttämällä työkalupalkin lisäämisen yhteydessä arvoaBor-derLayout.NORTH, saadaan työkalupalkki lisätty yläreunaan. Palkkivoi siis olla vaaka- tai pysty-suuntainen. Työkalupalkkiin voidaan lisätä sellaisia alkioita, jotka toteuttavatAction-rajapinnan. Eri-tyisesti nappulat toteuttavat tämän kyseisen rajapinnan,joten tyypillisesti työkalupalkkiin laitetaanJButton-alkioita, joiden ainoa sisältö on ikonikuva.

Työkalupalkkia voidaan raahata! Kokeile esimerkissä 8.11luodun työkalupalkin raahaamista jakiinnittämistä vasempaan pystyreunaan. Sen voi itse asiassa myös pudottaa koko ikkunan ulkopuolel-le, jolloin siitä tulee pieni ikkuna. Tällainen ns. Drag-and-Drop on mahdollista yleisemminkin, muttasitä 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 voivaihtaa 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.

Page 118: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classJToolBarDemo {

private static ImageIcon redBall, blueBall, greenBall, yellowBall;private static void luoVäriPallot() throws Exception {

String base ="http://www2.cs.utu.fi/icons/" ;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);// CENTERJMenuBar 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 classActionKuuntelija implementsActionListener {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

Page 119: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.9. DIALOGI-IKKUNAT 119

8.9 Dialogi-ikkunat

Swingissä on dialogeja varten luokkaJDialog. Se on hyvin samanlainen kuinDialog, dialogit ovatjoko modaalisia tai ei-modaalisia. AWT:stä poiketenJDialog ei tarvitse omistajaikkunaa. Aiemminjo tullut todetuksikin, että dialogeihin voi nyt liittyä menupalkki.

Luokan JDialog käytön tarvetta on Swingissä vähennetty apuluokanJOptionPane avulla. Sesisältää joukon staattisia metodeja, joiden avulla voidaan luoda tietynlaisia tavallisesti tarvittaviadialogi-ikkunoita. Metodilla ’showConfirmDialog’ luodaan yes/no/cancel-tyyppinen dialogi-ikkuna.Vastaavasti metodilla ’showInputDialog’ voi kysyä käyttäjältä tekstimuotoista vastausta johonkin yk-sinkertaiseen kysymykseen. Metodia ’showMessageDialog’käytetään monipuolisten tiedoitusdialo-gien esittämiseen. Lopuksi, metodilla ’showOptionDialog’ voidaan pyytää käyttäjää valitsemaan jokinuseista vaihtoehdoista.

Esimerkki 8.12 Dialogi-ikkunoiden tuottamista luokallaJOptionPane.

import java.awt.∗;import java.awt.event.∗;import javax.swing.∗;import java.util.∗;public classOptionPaneDemoextendsJApplet {

private ImageIcon kuva =new ImageIcon("burst.gif" );private JTextField tulos =newJTextField("Aluksi" );

public void init() {JButton b =newJButton("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 yksitoisensa perään. Alkutilannetta ja syntyviä dialogi-ikkunoita on havainnollistettu kuvissa 8.13 — 8.16.

Page 120: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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.

Page 121: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.10. TAULUKOT LUOKALLA JTABLE 121

8.10 Taulukot luokalla JTable

Swingissä on AWT:hen nähden täysin uudenlainen GUI-komponentti 2-ulotteisen taulukkomuotoi-sen dataesittämiseksija editoimiseksi. Tämä komponentti onJTable. Tällä taulukkokomponentillaon yhteys relaatiotietokantojen tauluihin mm. siinä mielessä, että sarakkeille voidaan asettaa otsikko-teksti. Taulukkokomponentti on monessa mielessä hyvin dynaaminen. Sisällön esittämisvaihtoehtojenmuodostamisen 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, muttarivin eri sarakkeilla voi luonnollisesti ollaerityyppistä tietoa. Taulukolla on oma malliolionsa, jokaon tyyppiäTableModel. Edellinen on tosinvain rajapinta, jolle Swingistä löytyy toteutuksia:AbstractTableModel ja DefaultTableModel. Tau-lukkokomponentin käyttäminen usein perustuu siihen, ettämääritellään itse komponentille malli, jokayleensä tehdään periyttämällä jommasta kummasta edellä mainitusta toteutuksesta. Esimerkiksi, joshalutaan esittää eksoottista tietoa taulukossa — tai vaikkapa rajoittaa editointitoimenpiteitä jollakintavalla — joudutaan usein määrittelemään uusi malli. Taulukkokomponentti poikkeaa muista GUI-komponenteista myös siinä mielessä, että vaikka siihen (jasen malliolioon liittyy tapahtumia), niinkoko 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 uudestaanmyös sisällön esittämisestä (renderöinnistä) huolehtivataulukkokomponentin osa. Tällaista rende-röinnin määrittelemistä ei tämän materiaalin puitteissa käsitellä. (On Java-kirjoja, jotka käsittelevättaulukkokomponenttia 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 vaiku-tusta malliolion sisältöön.

Yksinkertaisimmillaan taulukkokomponentin käytön yhteydessä ei itse tarvitse määritellä juurimitään. Laajimmillaan määritellään esimerkiksi alkioiden haku, talletus, renderöinti ja editointi. Mal-liolion kautta taulukkokomponentti on erittäin näppärä ajatellen GUI-ohjelmassa käsiteltävää tietoa— taulukkokomponentin sisällön editointi GUI:ssa voidaanhelposti tehdä sellaiseksi, että muutoksetkohdistuvat suoraan ohjelmassa olevaan 2-ulotteiseen taulukko-olioon.

LuokanJTable metodeita sen enempää kuin malliluokkien metodeitakaan eitässä esitellä. Näiltäosin kehoitetaan tutkimaan JDK:n dokumentaatiota. YlipäänsäJTable:n käyttö kaikessa laajuudes-saan 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 suo-rituksen seurauksena syntyy taulukkokomponentti, jonka solujen sisältöä voi editoida vapaasti. Ko-keile editointia ja aiemmin mainittua sarakkeiden raahaamista. Huomaa, että esimerkissä on asetettusarakkeille otsikot — oletusarvoisesti otsikot ovat ’A’, ’B’, ’C’, . . . . Kaikki taulukon rivit eivät olemahtuneet esille — taulukkokomponentti on tietoisesti laitettu ’main’-metodissaJScrollPane-olionsisään.

Esimerkkiin on liitetty nappula, jonka painamisen seurauksena (tapahtumankäsittelijän avulla)lasketaan pääohjelmassa luodun 2-ulotteisenInteger-tyyppisiä lukuja sisältävän taulukon lukujensumma. Huomaa, miten ’main’-metodissa luodaanJTable-olio luomalla sen malliolio käyttämälläitseluotua 2-ulotteista taulukko-oliota ’luvut’. Voisi ajatella, että solujen arvon editoiminen vaikuttaisimyös taulukko-olion ’luvut’ sisältöön. Näin ei kuitenkaantapahdu.

Page 122: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classTableDemoI {

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 =newJTextField();JButton b =newJButton("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 lu-kujen summaus toimii oikein! Esimerkin parempi toiminnallisuus johtuu siitä, että malliluokastaAb-stractTableModel on periytetty uusi malliluokka, jota käytetään ’main’-metodissa luotavanJTable-olion mallina. Toimivuus ohjelmaan ’TableDemoI’ nähden perustuu oikeastaan metodiin ’setValueAt’,jolla ei ole vastaavan sisältöistä toteutusta luokassaAbstractTableModel. Esimerkissä luodun malli-luokan ’IntegerTableModel’ kaikki metodit (konstruktoria lukuunottamatta) on peritty yliluokasta ja

Page 123: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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äyt-tää (jakamalla) sille konstruktorin kautta välitettävää 2-ulotteista kokonaislukutaulukkoa. Metodien’getRowCount’ ja ’getColumnCount’ merkitys on suoraviivainen: mallia käyttävä GUI-komponenttitarvitsee tiedon näistä. Metodin ’isCellEditable’ merkitys on kertoa GUI-komponentille, voiko solunsisältöä editoida. Metodilla ’getValueAt’ GUI-komponentti hakee mallioliolta solun sisällön ja muut-tuneen sisällön se puolestaan taltioi metodilla ’setValueAt’.

Page 124: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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äin2-ulotteiselta taulukolta, jonka alkioita voidaan havainnoida ja asettaa. PääohjelmassaJTable-olioluodaan hieman eri tavalla kuin ohjelmassa ’TableDemoI’, mutta muutoin pääohjelmassa ei pitäisiolla mitään erityistä.

Page 125: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.10. TAULUKOT LUOKALLA JTABLE 125

Esimerkki 8.14 LuokanJTable 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 classTableDemoII {

public static void main(String[] args) {JFrame f =new JFrame("JTable havainnollistus II" );Container c = f.getContentPane();c.setLayout(newFlowLayout());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(newJScrollPane(table));final JTextField tf =new JTextField();JButton b =new JButton("Summaa luvut" );c.add(b); c.add(tf);b.addActionListener(newActionListener(){

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

classIntegerTableModelextendsAbstractTableModel {private int [][] luvut;

IntegerTableModel(int [][] l) { luvut = l; }

public int getRowCount() {return luvut.length; }public int getColumnCount() {return luvut[0].length; }

public booleanisCellEditable(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

Page 126: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

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 classTableDemo {

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(newOmaTableModel(t,otsikot));c.add(newJScrollPane(table));f.setSize(400,400); f.setVisible(true);f.addWindowListener(new IkkunanSulkija());

} // main

static Object[][] teeTiedot()throws Exception {String base ="http://www.cs.utu.fi/icons/" ;String[] nimet = { "red" , "blue" , "green" , "yellow" , "pink" };Object[][] rivit = newObject[5][];for (int i=0; i<5; i++) {

ImageIcon ii =new ImageIcon(newURL(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

classOmaTableModelextendsDefaultTableModel {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) {caseNIMI: return String.class;caseKUVA: return ImageIcon.class;casePUHTAUS:return Boolean.class;caseKOODI: return Character.class;default: return super.getColumnClass(col);}

} // getColumnClass

public booleanisCellEditable(int row, int col) {if (col == KOODI) return false;return ((0≤ col) & (col < COLS) & (0≤ row) & (row ≤ getRowCount()));

} // isCellEditable} // OmaTableModel

Page 127: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.11. PUURAKENTEET LUOKALLAJTREE 127

Kolmas esimerkki 8.15 esittelee ohjelman ’TableDemo’, joka ulkoasu näkyy kuvasta 8.19. Edel-lisistä 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 malliluokkaperiyttämällä luokastaDefaultTable-Model. Tällä kertaa ei malliluokkaan rakenneta taulukon editoinnin mahdollistamista, joten vaik-ka esimerkiksi ikonikuvan takana olevaa URL-osoitetta voimennä muuttamaan, niin sillä tavalla eisaa aikaan uutta ikonia — tällainen editoitavuus olisi kylläkin voitu toteuttaa antamalla uusi toteu-tus metodille ’setValueAt’. Solujen editoitavuuden osalta on määritelty, että ’Koodi’-sarakkeen arvojaei saa edes GUI:ssa mennä muuttamaan (kokeile!). Muutoin esimerkin tarkoituksena on lähinnä ha-vainnollistaa, miten toteuttamalla metodi ’getColumnClass’ saadaan aikaan esimerkiksiImageIcon-tyyppisten arvojen näkyminen ikonina myös taulukossa. Samoin Boolean-tyyppiset arvot näkyvätrastituslaatikkoina.

8.11 Puurakenteet luokallaJTree

Tässä luvussa luodaan pikainen katsaus hyvin monipuoliseen GUI-komponenttiin, jolla voidaanesit-tää ja editoida hierarkisia puurakenteita. Kyseinen luokka on SwinginJTree. Luokan JTable ta-paan tämänkin luokan toiminnassa päähuomio on mallioliossa eikä niinkään tapahtumissa. EdelleenJTable:n tapaan seuraavassa pyritään lähinnä antamaan (esimerkkien avulla) vaikutelma siitä, mitäkomponentilla voi tehdä — yksityiskohtia ei käydä systemaattisesti läpi.

PerusajatusJTree-luokan takana ontiedostohierarkian esittäminen. Toteutuksen takana olevatideat ovat hyvin samanlaisia kuinJTable:ssa. LuokanJTree idea on vain esittää puuta. Tietosisällönmalliolio on tyyppiäTreeModel (rajapinta). Hierarkian esitys on vertikaalista (vrt. Windows). Jokai-nen rivi sisältää yhden alkion, joka on juuri, lehtisolmu tai sisäsolmu. Puun alipuu voi ollaavattuna(displayed) taisuljettuna(collapsed). Edellinen on luonnollisesti sisäsolmujen jajuuren ominaisuus.

Rivillä esitetään oletusarvoisesti tekstiä, mutta voidaan esittää muutakin, esim. kuvia. Renderöin-tiä varten on luokkia, jotka eksoottisemmassa tapauksessapitää määritellä uudelleen (ei käsitellä). EriLook&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 (rajapin-ta) taiDefaultMutableTreeNode. Puukomponentin yhteydessä käytettävän malliluokanTreeModel

Page 128: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

128 LUKU 8. SWING-KOMPONENTIT

Kuva 8.20: PuuEsitys suorituksessa (windows).

(rajapinta) toteuttaa konkreettisesti luokkaDefaultTreeModel. OsaJTree-luokkaan liittyvistä luokis-ta on paketissajavax.swing.tree .

Kuva 8.21: PuuEsitys suorituksessa (motif).

Malliolioon kohdistuneet muutokset tuottavat tapahtuman, joten toiminnallisuuden voi rakentaakiinnittämällä puuolioon tapahtumankäsittelijöitä eri toimenpiteitä varten: valinta, poisto, lisäys, avaa-minen, . . . . Toisaalta taulukkokomponentin tapaan toiminnallisuus voidaan myös muodostaa johta-malla uusi tarkoituksen mukainen malliluokka puuoliolle.

Page 129: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.11. PUURAKENTEET LUOKALLAJTREE 129

Esimerkki 8.16 Vakiomuotoisen puukomponentin rakentaminen.

import javax.swing. ∗;import javax.swing.tree. ∗;public classPuuEsitys {

public static void main(String[] args) {JFrame kehys =newJFrame("PuuEsitys - demo" );kehys.setSize(300,200);kehys.addWindowListener(new IkkunanSulkija());// PuurakenneDefaultMutableTreeNode juuri, lintu, nisäkäs, matelija,

pöllö, haukka, ruskoSuoHaukka, hiiriHaukka, hiiriPöllö,tunturiPöllö;juuri = new DefaultMutableTreeNode("Eläin" );matelija =newDefaultMutableTreeNode("Matelija" );lintu = new DefaultMutableTreeNode("Lintu" );nisäkäs =new DefaultMutableTreeNode("Nisäkäs" );pöllö = newDefaultMutableTreeNode("Pöllö" );haukka =new DefaultMutableTreeNode("Haukka" );ruskoSuoHaukka =newDefaultMutableTreeNode("Ruskosuohaukka" );hiiriHaukka =newDefaultMutableTreeNode("Hiirihaukka" );hiiriPöllö = new DefaultMutableTreeNode("Hiiripöllö" );tunturiPöllö =newDefaultMutableTreeNode("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ö);

// PuuolioJTree puu =newJTree(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

Page 130: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

130 LUKU 8. SWING-KOMPONENTIT

Esimerkin 8.16 ohjelmalla ’PuuEsitys’ (jota on havainnollistettu kuvissa 8.20 ja 8.21) tuotetaanvakiomuotoinen puuolio. Esimerkin ideana on lähinnä havainnollistaa, miten solmurakenne voidaanmuodostaa, ja miten solmurakenteen juuren avulla voidaan rakentaaJTree-tyyppinen olio. Huomaataaskin, että puuolioon ei automaattisesti liity liu’uttimia.

Kuva 8.22: PuuEditori suorituksessa.

Kuvassa 8.22 on havainnollistus hieman monipuolisemmastaohjelmasta ’PuuEditori’ (esimerk-ki 8.17). Esimerkin ohjelmalla on alunperin tietynlainen puurakenne1, joka esittää maantieteellistenalueiden hierarkiaa. Ohjelmalla on mahdollista lisätä uusia solmuja eri tasoille — samoin solmujen tu-hoaminen on mahdollistettu. Kuvassa 8.22 oleva tilanne ei ole alkuperäisen tilanteen mukainen, vaansolmuja on poistettu, nimiä on muutettu ja uusia solmuja on lisätty.

Ohjelmassa ’PuuEditori’ toiminta ei perustu malliluokan määrittelemiseen uudelleen sen enem-pää kuin puuolion tuottamiin tapahtumiinkaan. Kuvassa 8.17 näkyvään kolmeen nappulaan ohjelman’main’-metodi kiinnittää tapahtumankäsittelijät, joiden avulla puurakennetta muutetaan. Tapahtuman-käsittelijöissä toiminta perustuu olennaisesti siihen, että mallioliosta voidaan selvittää, mikä on vii-meisin valittu puun solmu — toiminta rakennetaan sen suhteen.

1Olisi luonnollisesti ollut paljon parempaa, jos puurakenne olisi luettu aluksi jostakin tiedostosta.

Page 131: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

8.11. PUURAKENTEET LUOKALLAJTREE 131

Esimerkki 8.17 Ohjelma ’PuuEditori’, jolla voi editoida puurakennetta (1/2).

import java.awt. ∗;import java.awt.event. ∗;import javax.swing. ∗;import javax.swing.tree. ∗;public classPuuEditori {

private static JTree puu;private static DefaultTreeModel malli;

public static void main(String[] args) {JFrame kehys =new JFrame("PuuEditori" );kehys.setSize(300,300);kehys.addWindowListener(newIkkunanSulkija());Container sisältö = kehys.getContentPane();// PuuTreeNode juuri = teePuuRakenne();malli = new DefaultTreeModel(juuri);puu =new JTree(malli);puu.setEditable(true); // editoitavuussisältö.add(newJScrollPane(puu), BorderLayout.CENTER);// NappulatJButton lisääRinnalle, lisääAlle, tuhoa;lisääRinnalle =new JButton("Lisää rinnalle" );lisääAlle =newJButton("Lisää alle" );tuhoa =new JButton("Tuhoa" );JPanel p =new JPanel();p.add(lisääRinnalle); p.add(lisääAlle); p.add(tuhoa);sisältö.add(p, BorderLayout.SOUTH);// Nappuloiden toiminnotlisääRinnalle.addActionListener(newRinnalleAction());lisääAlle.addActionListener(newAlleAction());tuhoa.addActionListener(newTuhoaAction());kehys.setVisible(true);

} // main

private static TreeNode teePuuRakenne() {DefaultMutableTreeNode maailma, eurooppa, aasia, afrikka,

amerikka;maailma = tn("Maailma" );eurooppa = tn("Eurooppa" );aasia = tn("Aasia" );afrikka = tn("Afrikka" );amerikka = tn("Amerikka" );eurooppa.add(tn("Suomi" )); eurooppa.add(tn("Ruotsi" ));eurooppa.add(tn("Puola" )); eurooppa.add(tn("Saksa" ));aasia.add(tn("Kiina" )); aasia.add(tn("Etelä-Korea" ));afrikka.add(tn("Etelä-Afrikka" )); afrikka.add(tn("Tunisia" ));maailma.add(eurooppa); maailma.add(aasia);maailma.add(afrikka); maailma.add(amerikka);return maailma;

} // teePuuRakenne

private static DefaultMutableTreeNode tn(String s) {return new DefaultMutableTreeNode(s);

} // tn

Page 132: Johdanto - utustaff.cs.utu.fi/opinnot/kurssit/KLT/moniste/klt-L1-L8.pdf · Johdanto Tämän oppimateriaalin tarkoituksena on johdatella lukija nykypäivän ohjelmointikieliin liittyvään

132 LUKU 8. SWING-KOMPONENTIT

Esimerkki 8.17. Ohjelma ’PuuEditori’, jolla voi editoida puurakennetta (jatkoa 2/2).

static classAlleAction implementsActionListener {public void actionPerformed(ActionEvent e) {

DefaultMutableTreeNode valittu = (DefaultMutableTreeNode)puu.getLastSelectedPathComponent();// viimeksi valittu

if (valittu == null ) return ;DefaultMutableTreeNode uusi = tn("Uusi" );malli.insertNodeInto(uusi, valittu, valittu.getChildCount());

} // actionPerformed} // class AlleAction

static classRinnalleActionimplementsActionListener {public void actionPerformed(ActionEvent e) {

DefaultMutableTreeNode valittu = (DefaultMutableTreeNode)puu.getLastSelectedPathComponent();// viimeksi valittu

if (valittu == null ) return ;DefaultMutableTreeNode uusi = tn("Uusi" );DefaultMutableTreeNode p = (DefaultMutableTreeNode)valittu.getParent();if (p 6= null ) malli.insertNodeInto(uusi, p, p.getIndex(valittu) + 1);

} // actionPerformed} // class RinnalleAction

static classTuhoaActionimplementsActionListener {public void actionPerformed(ActionEvent e) {

DefaultMutableTreeNode valittu = (DefaultMutableTreeNode)puu.getLastSelectedPathComponent();// viimeksi valittu

if (valittu == null ) return ;if (valittu.getParent()6= null ) malli.removeNodeFromParent(valittu);

} // actionPerformed} // class TuhoaAction

} // class PuuEditori

8.12 Yhteenveto

Tämän luvun tarkoituksena on ollut antaa lukijalle käsityssiitä, millaisia Swingin GUI-komponentitovat. Komponentteja on paljon ja niihin liittyy runsaasti yksityiskohtia. Tarkastelu on väistämättäjäänyt yksityiskohtien osalta pinnalliseksi.

Swing-komponentit ovat monipuolisempia ja usein näyttävämpiä. Toisaalta moni asia on ratkaistuhyvin samanlaisesti kuin AWT:ssä (AWT:nComponent toimii yhtenä yliluokkana . . . ).

Olennaisin uusia asia onmalliolion liittyminen lähes kaikkiin Swingin komponentteihin. Mallio-lio voidaan jakaa. Malliluokka voidaan määritellä itse uudelleen. Malliluokan määrittely uudelleen onolennainen osa kompleksisia komponentteja:JTable ja JTree. Uutta on myös, että malliin kohdistu-neet muutokset aiheuttavat tapahtumia, joihin voi reagoida kiinnittämällä malliolioon tapahtumankä-sittelijän!