Luku 12.4: Käyttöliittymiä Swing-kirjastolla

../_images/robot_fight.png

Johdanto

Ensin: laamaohjelma

Nouki käyttöön moduuli SwingExamples ja aja o1.llama.LlamaApp.

Ohjelmassa on graafinen käyttöliittymä, jossa on yksi kolmiosainen ikkuna: ylhäällä tekstiä, keskellä kuva ja alhaalla nappula. Ohjelma reagoi hiiren näykkäisyihin ja rullaan. Kokeile.

Luvun juoni

Luvuissa 2.7 ja 3.1 opetettiin, että voit kuvata käyttöliittymän olioina ja laatia metodeita, jotka toimivat tapahtumankäsittelijöinä. Ja siinä jo pitkälti täyttyivätkin kurssin viralliset oppimistavoitteet GUI-ohjelmoinnin osalta. Silti on hauskaa ja hyödyllistä opetella hieman lisää peruskäsitteitä ja taitoja tästä konkreettisesta aiheesta; siksi tämä vapaaehtoinen luku.

Jo kurssin alussa totesimme, että graafiset käyttöliittymät rakennetaan jollakin tarkoitukseen sopivalla kirjastolla. Tähän saakka kurssilla olet käyttänyt O1:n omaa kirjastoa, erityisesti sen luokkia View ja Pic. Tässä luvussa opit vähän erilaisten graafisten käyttöliittymien laatimisesta toisella kirjastolla.

Otetaan tavoitteeksi, että ymmärrät, miten esimerkiksi äskeisen laamaohjelman käyttöliittymä on toteutettu. On kuitenkin järkevää aloittaa yksinkertaisemmista esimerkeistä, joiden kautta opit käsittelemään yksittäisiä graafisen käyttöliittymän rakennusosasia eli käyttöliittymäelementtejä (GUI elements tai GUI components). Edetään seuraavassa järjestyksessä:

  1. muutama sana GUIn laatimistyökaluista yleisesti;

  2. yksittäisiä käyttöliittymien osia REPLissä: nappulat, teksti, yms.; niiden ominaisuuksien asettaminen;

  3. usean elementtien asemointi käyttöliittymäksi;

  4. tuollaisen käyttöliittymän tallentaminen sovellukseksi;

  5. tapahtumiin reagoiminen: nappulanpainallukset, hiiren rulla ja liikkeet yms.;

  6. laamaohjelman käyttöliittymän toteutus;

  7. toinen esimerkki: käyttöliittymä pupputekstigeneraattorille; siihen liittyvä tehtävä.

GUI-kirjastoista

Scala-kielen kirjastoihin lukeutuu graafisten käyttöliittymien laatimiseen tarkoitettu kirjasto nimeltä Swing. Muitakin käyttöliittymäkirjastoja on, mutta käytämme tässä luvussa nimenomaan Swingiä.

O1:n View ja Pic ovat erityisen käteviä, kun haluttu käyttöliittymä on suhteellisen yksinkertainen ja muodostuu kuvista tai geometrisista kuvioista, joita asemoidaan suhteessa toisiinsa. Se tarjoaa myös helppokäyttöisen mutta rajallisen tuen tasaisesti tikittävälle ajalle.

Swing on kankeampi käyttää mutta sopii useammanlaisiin tarkoituksiin.

Swing-kirjasto sisältää luokkia, jotka kuvaavat erilaisia GUI-elementtejä: nappuloita, ikkunoita, tekstikenttiä, valikoita jne. Omassa käyttöliittymässäsi voit käyttää näiden luokkien ilmentymiä ja yhdistellä niitä. Voit myös johtaa luokista uudenlaisia elementtityyppejä periyttämällä.

Alla on uusintana lukua 7.5 koristanut kaavio, jossa näkyy eräitä Swing-kirjaston luokkia. Luokat eivät ole vielä tuttuja, mutta useimpiin niistä tutustutaan tässä luvussa.

../_images/inheritance_swing.png

Mitä muita grafiikkakirjastoja on kuin Swing?

Java-puolella Swingin vaihtoehtoihin lukeutuu JavaFX, jonka Scala-versio on ScalaFX.

Omanlaisiaan työkaluja käyttöliittymien laatimiseen tarjoavat mm. Qt Jambi, Tcl/Tk ja niin edelleen.

Funktionaalisessa GUI-ohjelmoinnissa voidaan hyödyntää reaktiivista ohjelmointitapaa.

Kun kyseessä on verkkosovellus, niin käyttöliittymän roolissa voi olla web-sivu tai -sivusto. Web-sivuihin voi liittää Scalallakin kirjoitettua käyttöliittymäkoodia, koska Scalan voi kääntää selaimessa toimivaksi JavaScriptiksi Scala.js-työkalulla.

Graafisen käyttöliittymän osia

Ensimmäinen ikkunaolio

Kokeillaan elementtien luomista REPLissä. Kokeile itse mukana! Valitse REPLiä käynnistäessäsi moduuli SwingExamples.

Aloitetaan luomalla ikkuna eli Frame. Näistä ikkunaolioista voivat tulla mieleen o1-pakkauksen View-oliot.

import scala.swing.*val ikkuna = Frame()ikkuna: scala.swing.Frame = scala.swing.Frame$$anon$1[frame0,0,0,0x0,invalid,hidden, ... ]

Vaikka ikkunaa ei ruudulle vielä ilmestynytkään, nyt on luotu uusi ikkunaa kuvaava Frame-olio. Sen toString-metodi palauttaa pitkän luettelon erilaisia ikkunan ominaisuuksia, jotka tulostuvat REPLiin.

Tämän ikkunaolion kykyihin kuuluu, että se osaa huolehtia itseään vastaavien pikselien piirtämisestä tietokoneen ruudulle. Se ei kuitenkaan vielä tee tätä, koska luotu Frame-olio on oletusarvoisesti piilotetussa tilassa. Ikkuna pitää erikseen käskeä näkyviin:

GUI-elementin ominaisuudet

Näkyvyyttä säätelee ikkunaolion visible-muuttuja:

ikkuna.visible = trueikkuna.visible: Boolean = true

Heti, kun annat tämän käskyn, ikkuna tulee näkyviin. Se voi tosin olla ensin vaikea huomata, koska se on pieni ja voi olla näytön yläkulmassa. (Saatat joutua etsimään sen tehtäväpalkista tai vastaavasta paikasta työskentely-ympäristössäsi.)

../_images/gui1.png

Voit liikuttaa ikkunaa ja muuttaa sen kokoa samaan tapaan kuin käyttämässäsi ympäristössä ikkunoille muutenkin voi tehdä.

Jos tulet näpäyttäneeksi ikkunansulkemisraksia, ja ikkuna poistuu näkyvistä, niin saat sen takaisin näkyviin antamalla viimeksi mainitun käskyn uudestaan.

Luodulla ikkunalla ei vielä ole otsikkoa. Kunkin Frame-olion otsikon määrää title-niminen ominaisuus. Sille voi sijoittaa uuden arvon:

ikkuna.title = "Kokeilu"ikkuna.title: String = Kokeilu

Kokeile, niin huomaat, että otsikko piirtyy ruudulle saman tien.

Nappula ikkunan sisälle

Luodaan ikkunaan nappula. Nappuloita kuvaa luokka Button, josta voi luoda ilmentymän näin:

val nappula = Button("Klikkaa tästä")( () )nappula: scala.swing.Button = scala.swing wrapper scala.swing.Button$$anon$1[,0,0,0x0, ... ]

Nappulanluontikäskyllä on kaksi parametriluetteloa. Ensimmäisessä annamme nappulan tekstin ja toisessa...

... on nyt vain tyhjät sulut sen merkiksi, että tämä nappula ei nyt tee mitään. Jäljempänä opit kaksikin eri tapaa, jolla nappulaan voi liittää toimintoja.

Jälleen olemme luoneet GUI-elementin, mutta mitään uutta ei ilmesty ruudulle. Tämä johtuu siitä, että vaikka meillä on nyt nappulaolio ja näkyvä ikkuna, niin missään ei ole määritelty, että kyseisen nappulan pitäisi olla juuri kyseisessä ikkunassa.

Jos emme halua ikkunaan mitään muuta kuin yhden nappulan, niin asia hoituu näin:

ikkuna.contents = nappulaikkuna.contents: Seq[scala.swing.Component] = List(scala.swing wrapper scala.swing.Button$$anon$1 ... )

Nyt ikkuna näyttää suunnilleen tältä:

../_images/gui2-fi.png

Voit painaa nappulaa. Pinnallisin puolin se toimii, joskaan painamisesta ei varsinaisesti seuraa mitään.

Swing-Framella ei siis ole makePic-metodia kuten o1.View-oliolla (luku 2.7). Emme muodosta Swing-ikkunan "kuvaa" vaan koostamme sen sisällön GUI-elementeistä. Vastaavasti myös työkalut Swing-elementtien asemointiin ovat toisenlaiset kuin o1.Pic-olioista tutut place, leftOf jne.

Elementtien asemointia paneeleilla

Ikkunan contents-muuttuja määrää, mikä elementti ikkunan varsinaisena sisältönä näkyy (poislukien esim. yläreunan otsikkorivi, jos sellainen on). Yllä siis määrittelimme, että ikkunan sisällön muodostaa luomamme nappulaolio.

contentsin arvoksi voi noin sijoittaa yksittäisen elementin. Yleensä kuitenkin haluaisimme, että ikkunassa on useita elementtejä. Voisimme haluta luoda vaikkapa tällaisen ikkunan:

../_images/gui3.png

Ongelman ratkaisee se, että GUI-elementit voivat olla sisäkkäin. Yhteen liittyvät elementit voidaan ryhmittää yhdeksi paneeliksi (panel), jonka voi asettaa ikkunan sisällöksi. Paneeli on elementti, joka voi sisältää useita toisia elementtejä.

Katsotaan äskeistä kuvaa tarkemmin:

Ikkunaa otsikkoriveineen kuvaa Frame-olio.

Nimiö eli "lappu" (label) on elementti, jossa on tekstiä, kuva tai molemmat. Nimiöitä kuvaa luokka scala.swing.Label.

Nappuloita kuvaa luokka scala.swing.Button, kuten jo nähtiinkin.

Ikkunan sisällöksi ei ole tässä kirjattu mitään nappulaa tai Labeliä vaan nämä kaikki sisältävä paneelielementti. Kyseisen paneelin ominaisuuksiin kuuluu muun muassa se, että sen sisältö asemoidaan allekkain.

Tällaisen ikkunan luominen ei ole vaikeaa. Aloitetaan vaikkapa luomalla halutut nappulat ja tekstilappu:

val ekaNappi = Button("Paina minua")( () )ekaNappi: scala.swing.Button = scala.swing wrapper scala.swing.Button$$anon$1[,0,0,0x0, ... ]
val tokaNappi = Button("Ei kun MINUA!")( () )tokaNappi: scala.swing.Button = scala.swing wrapper scala.swing.Button$$anon$1[,0,0,0x0, ... ]
val kehote = Label("Paina jompaakumpaa napeista.")kehote: scala.swing.Label = scala.swing wrapper scala.swing.Label$$anon$1[,0,0,0x0, ... ]

Luodaan sitten paneeli, joka kokoaa nämä yhteen:

val kaikkiJutut = BoxPanel(Orientation.Vertical)kaikkiJutut: scala.swing.BoxPanel = scala.swing wrapper scala.swing.BoxPanel$$anon$1[,0,0,0x0,invalid,
layout=javax.swing.BoxLayout, alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=]

BoxPanel on eräs paneelityyppi. BoxPanel-olio edustaa paneelia, johon paneelin sisältö on sijoitettu joko vierekkäin tai allekkain.

Arvo Vertical on vakio. Sitä käytetään tässä merkkinä siitä, että luotavan paneelin sisältö on tarkoitus sijoittaa "pystysuoraan" eli allekkain. (Vertical on yksittäisolion Orientation muuttuja; samaan yhteyteen on määritelty myös arvo Horizontal.)

Paneelilla on contents-ilmentymämuuttuja, jonka tyyppi on Buffer[Component] eli puskurillinen GUI-komponentteja. Voimme lisätä puskuriin komponentteja joko yksi kerrallaan tuttuun tyyliin kaikkiJutut.contents += kehote tai useita kerralla ++=-metodilla näin:

kaikkiJutut.contents ++= Vector(kehote, ekaNappi, tokaNappi)res0: Buffer[Component] = Buffer(scala.swing wrapper scala.swing.Label$$anon$1[,0,0, ... ]

Näin saimme aikaan paneelin, joka sisältää allekkain kehotteen ja nappulat. Luodaan vielä ikkuna ja asetetaan sen sisällöksi tämä paneeli:

val nappulaikkuna = Frame()nappulaikkuna: scala.swing.Frame = scala.swing.Frame$$anon$1[frame1,0,0,0x0, ... ]
nappulaikkuna.contents = kaikkiJututnappulaikkuna.contents: Seq[scala.swing.Component] = List(scala.swing wrapper scala.swing.BoxPanel ... )
nappulaikkuna.visible = truenappulaikkuna.visible: Boolean = true

Nyt näkyviin ilmestyy kaksinappulainen ikkuna.

Huomasithan, että järjestys, jossa puskurin sisältämät oliot luotiin, ei millään tavoin määrää sitä, miten komponentit asemoituvat ikkunaan? Asemoinnin määrää se järjestys, jossa oliot lisättiin paneeliin — tässä tapauksessa kehote ensin ja nappulat sen perään.

Kuvia

Yllä käytimme Label-luokkaa tekstin esittämiseen käyttöliittymässä. Tuosta luokasta on moneksi: Label-"lappusessa" voi olla tekstin sijaan myös kuva. (Voi myös olla molemmat, jolloin teksti toimii kuvatekstinä.)

Olemassa olevien kuvien lataamiseen sopii Swingin luokka ImageIcon; ImageIcon-oliota luodessa voi ilmoittaa, mistä kuvan sisältö noudetaan. Kuvan voi ladata esimerkiksi paikallisesta tiedostosta moduulin kansiossa, muualta omalta koneeltasi tai netti-URL:ista. (Tältä osin ImageIcon on kuin o1.Pic.)

Tehdään kokeeksi vaikkapa tällainen ikkuna:

../_images/gui4.png

Alla olevassa REPL-esimerkissä tämä saadaan aikaan luomalla Label, jossa on internetistä ladattu kuva.

importataan aluksi kuvia edustava luokka ImageIcon ja nettiosoitteiden käsittelyyn käyttämämme luokka URL:

import javax.swing.ImageIconimport java.net.URL

Käytämme tässä alun perin Java-kielen yhteyteen määriteltyjä kirjastoja.

Seuraavaksi luodaan Label, jossa ei ole tekstiä lainkaan:

val kuvakomponentti = Label()kuvakomponentti: scala.swing.Label = scala.swing wrapper scala.swing.Label$$anon$1[,0,0,0x0, ... ]

Tekstin sijaan sijoitamme sen "ikoniksi" eli kuvaksi ImageIcon-olion:, jota vastaava kuva ladataan eräästä URLista:

val esimerkkikuvanURL = URL("https://gitmanager.cs.aalto.fi/static/O1_2024/_static/pics/misc/aiolos.jpg")esimerkkikuvanURL: URL = https://gitmanager.cs.aalto.fi/static/O1_2024/_static/pics/misc/aiolos.jpg
kuvakomponentti.icon = ImageIcon(esimerkkikuvanURL)

Luodaan vielä uusi ikkuna, jonka sisältönä on kuvan sisältävä Label:

val kuvaikkuna = Frame()kuvaikkuna: scala.swing.Frame = scala.swing.Frame$$anon$1[frame5,0,0,0x0, ... ]
kuvaikkuna.contents = kuvakomponenttikuvaikkuna.contents: Seq[scala.swing.Component] = List(scala.swing wrapper ... )
kuvaikkuna.visible = truekuvaikkuna.visible: Boolean = true

Näkyviin ilmestyy sellainen ikkuna kuin yllä kuvattiin.

javax.swing?

Erään yllä käytetyn pakkauksen nimi on javax.swing, mikä voi tuntua oudolta. Käyttämämme Scalan GUI-kirjasto on rakennettu Java-kielen samannimisen kirjaston varaan.

Scala-ohjelmoidessakin käytetään joitakin Java-kirjastoja suoraan silloin kun se on kätevää. Ainakaan nykyisessä Scalan Swing-kirjastossa ei ole omaa ImageIcon-luokkaa tai vastaavaa, mutta ei tarvitakaan, koska voimme käyttää Java-Swingin luokkaa.

Kirjain x — eXtension — on Javan Swing-pakkauksen nimessä historiallisesta syystä.

Apuikkunoita eli dialogeja

Väliaikaisesti ruudulla näkyviä "apuikkunoita" eli dialogeja (dialog) käytetään erilaisiin tarkoituksiin kuten viestien välittämiseen käyttäjälle, valintojen tekemiseen ja muuhun syötteen pyytämiseen käyttäjältä.

Monet dialogit ovat pieniä ikkunoita, joissa on muutama elementti aseteltuna tietyllä eri ohjelmissa samanlaisena toistuvalla tavalla. Esimerkiksi viestin välittämiseen käyttävä ikkuna voi muodostua pienestä kuvakkeesta, viestistä ja OK-napista näin:

../_images/gui5-fi.png

Dialogi-ikkunoita kuvaa luokka scala.swing.Dialog. Monissa yksinkertaisissa tilanteissa siitä ei tarvitse nimenomaisesti luoda ilmentymää, vaan voimme käyttää Dialog-kumppaniolion käteviä metodeita. Käsittelemme tässä niistä vain yhden nimeltä showMessage, jota voi käyttää näin:

Dialog.showMessage(kuvakomponentti, "Ave, Munde!", "This is a message")

showMessagen ensimmäinen parametri määrää, minkä komponentin päälle apuikkuna ilmestyy. Tässä se määrätään ilmestyväksi ylempänä luodun kuvan keskelle.

Jälkimmäiset parametrit määräävät viesti-ikkunan sisällön ja otsikon.

Äskeinen käsky luo kuvatunlaisen apuikkunan ja tuo sen näkyviin. Apuikkuna pysyy esillä, kunnes käyttäjä sulkee sen yläkulman rastista tai OK-napilla.

Swing-käyttöliittymä osana sovellusta

Toistaiseksi puuhasimme REPLissä. Tarkoituksena olisi kuitenkin saada komponenttien luomiskäskyt tallennettua tiedostoon, jossa ne muodostavat sovelluksen käyttöliittymän.

Alla on yksi tapa muodostaa sovellus, jossa on samanlainen kaksinappulainen käyttöliittymä kuin yllä.

object Komponenttikokeilu extends SimpleSwingApplication:
  // Pääkomponentit:
  val ekaNappi  = Button("Paina minua")( () )
  val tokaNappi = Button("Ei kun MINUA!")( () )
  val kehote = Label("Paina jompaakumpaa napeista.")

  // Komponenttien asemointi ikkunaan:
  val kaikkiJutut = BoxPanel(Orientation.Vertical)
  kaikkiJutut.contents ++= Vector(kehote, ekaNappi, tokaNappi)
  val nappulaikkuna = MainFrame()
  nappulaikkuna.contents = kaikkiJutut
  nappulaikkuna.title = "Kokeiluikkuna"
                              
  // Sovelluksen pääikkunan palauttava metodi:
  def top = this.nappulaikkuna

end Komponenttikokeilu

Sovellustamme kuvaa yksittäisolio Komponenttikokeilu, joka periytyy luokasta SimpleSwingApplication. SimpleSwingApplication-tyyppinen olio voi toimia ohjelman käynnistysoliona (vrt. extends App; luku 2.7).

Komponentit luodaan tässä muuten ihan samoilla käskyillä kuin REPLissäkin teimme, paitsi että käytetään luokkaa MainFrame Framen sijaan. MainFrame on Framen aliluokka. Sen lisäominaisuutena on, että se päättää koko sovelluksen suorituksen, kun kyseinen ikkuna suljetaan (vrt. tavallinen Frame vain piilottuu suljettaessa, mutta ohjelma jää käyntiin). Tämä on toivottavaa erillisessä sovelluksessa, muttei REPLissä.

SimpleSwingApplication on abstrakti luokka. Sovellusta kuvaavan oliomme on toteutettava yliluokkansa abstraktiksi jättämä top-niminen metodi, joka kertoo paluuarvollaan, mikä on kyseisen sovelluksen pääikkuna. SimpleSwingApplication-yliluokka huolehtii siitä, että pääikkuna tulee näkyviin sovelluksen käynnistyessä.

Juuri mainitusta syystä emme tarvitse ohjelmaan erillistä käskyä nappulaikkuna.visible = true, jollainen REPLissä tarvittiin.

Tämä koodi löytyy myös SwingExamples-moduulin luokasta o1.swingtest.ComponentTestApp (englanniksi). Voit kokeilla ajaa ohjelman ja tehdä siitä variaatioita. Kokeile esimerkiksi Horizontalia Verticalin tilalla.

Vaihtoehtoinen merkintätapa: sisäkkäiset määrittelyt

Luvun 7.5 valinnaisessa materiaalissa näytettiin, että luokalle voi määritellä kertakäyttöisen alatyypin "lennosta", samalla kun luo siitä yhden ilmentymän. Vastaava toimii myös Swing-luokkien kanssa. Esimerkiksi seuraavat kaksi tekevät saman.

// Versio 1: sama kuin edellä
val nappulaikkuna = MainFrame()
nappulaikkuna.contents = kaikkiJutut
nappulaikkuna.title = "Kokeiluikkuna"
// Versio 2: uusi `MainFrame`\n aliluokka ja siitä ilmentymä
val nappulaikkuna = new MainFrame:
  this.contents = kaikkiJutut
  this.title = "Kokeiluikkuna"

Huomaa new. (Ks. luku 7.5)

this-sanat voisi kakkosversiosta jättää poiskin. Ne eivät ole tässä teknisesti välttämättömät eivätkä oikein selkiytäkään koodia.

Tässä vielä koko äskeinen ohjelma kirjoitettuna jälkimmäisellä tyylillä ja muutenkin hieman tiiviimmin. Tämä merkintätapa korostaa komponenttien sisäkkäisyyttä.

import scala.language.adhocExtensions

object Komponenttikokeilu extends SimpleSwingApplication:

  val ekaNappi  = Button("Paina minua")( () )
  val tokaNappi = Button("Ei kun MINUA!")( () )
  val kehote = Label("Paina jompaakumpaa napeista.")

  val nappulaikkuna = new MainFrame:
    title = "Kokeiluikkuna"
    contents = new BoxPanel(Orientation.Vertical):
      contents ++= Vector(kehote, ekaNappi, tokaNappi)

  def top = this.nappulaikkuna

end Komponenttikokeilu

Alun import kiertää ongelman, että käyttämiämme Java-luokkia ei ole julistettu avoimiksi (open; luku 7.5). Ilman import-käskyä saisimme kääntäjältä varoituksen.

Tapahtumankäsittelyä Swing-kirjastolla

Pikakertaus: käyttöliittymätapahtumat, -kuuntelijat ja -käsittelijät

Luvusta 3.1:

  • Käyttöliittymätapahtumia (GUI event) ovat esimerkiksi hiiren klikkaukset ja liikkeet, näppäimen painallukset ja niin edelleen.

  • Tapahtumankäsittelijä (event handler) on aliohjelma, joka suoritetaan reaktiona tapahtumaan.

    • o1.View-olioissa määrittelimme tapahtumankäsittelijöiksi metodeita kuten onClick ja onTick.

  • Tapahtumankuuntelija (event listener) on olio, jolle raportoidaan tietynlaisista tapahtumista ja joka reagoi tapahtumiin suorittamalla tapahtumankäsittelijän.

    • o1.View-olio toimii tapahtumankuuntelijana itselleen: se vastaanottaa itse tiedon siitä, kun jotakin tapahtuu, ja kutsuu silloin tapahtumankäsittelijämetodeitaan.

Kätevä tapa liittää toimintoja nappuloihin

Ennen kun tarkastelemme tapahtumankäsittelyä Swingissä yleisemmin, katsotaan eräs yksinkertainen tapa, jolla voit liittää nappulaan toiminnon. Tämä tapa on kätevä mutta riittää vain yksinkertaisimpiin tarpeisiin.

Koodissamme luki ennestään näin:

val ekaNappi  = Button("Paina minua")( () )
val tokaNappi = Button("Ei kun MINUA!")( () )

Voimme kirjoittaa tuohon tyhjien sulkeiden kohdalle muutakin, vaikkapa tulostuskäskyn:

val ekaNappi  = Button("Paina minua")( println("Kiitos kun painoit.") )
val tokaNappi = Button("Ei kun MINUA!")( println("Jee, valitsit MINUT!") )

Tulostuskäskyt toimivat nyt tapahtumankäsittelijöinä, jotka nappulat suorittavat painettaessa. (Kyseessä on evaluoimaton eli by name -parametri; luku 7.2. Se evaluoidaan aina tapahtuman sattuessa.) Button-olio osaa toimia omana tapahtumankuuntelijanaan.

Usein on mielekästä määritellä erillinen funktio tapahtumien käsittelyyn. Tehdään vaikkapa funktio, joka näyttää dialogi-ikkunassa viestin, kun nappulaa painetaan. Sekä seuraava että edellinen esimerkki löytyvät kokonaisena SwingExamples-moduulista; voit kokeilla.

val ekaNappi  = Button("Paina minua")( reagoiPainallukseen() )
val tokaNappi = Button("Ei kun MINUA!")( reagoiPainallukseen() )

def reagoiPainallukseen() =
  val viesti = "Painoit jotakin napeista."
  Dialog.showMessage(kaikkiJutut, viesti, "Viesti")

Äskeiset temput liittyivät nimenomaan napinpainalluksiin. Ne eivät myöskään tarjonneet mahdollisuutta tutkia tapahtuman tietoja tarkemmin. Katsotaan nyt, miten pääsemme käsiksi Swing-tapahtumiin joustavammin erilaisissa tilanteissa.

Käyttöliittymätapahtumat ja Swing

Swing-käyttöliittymäkomponentteja kuvaavat oliot osaavat tapahtuman sattuessa ilmoittaa tapahtumasta kuuntelijoilleen. Sovellusohjelmoijan erikseen päätettäviksi jäävät:

  1. Mille oliolle tapahtumista tulisi ilmoittaa niiden sattuessa? Toisin sanoen: mikä tai mitkä oliot toimivat kunkin Swing-komponentin tapahtumankuuntelijoina?

    • Tätä emme o1-kirjastolla tehneet, vaan View-olio toimi omana kuuntelijanaan.

  2. Minkä ohjelmakoodin kuuntelija suorittaa, kun nappulaa painetaan, hiirtä liikutetaan tms.? Eli: mitä tapahtumasta pitäisi seurata?

    • Tämän määrittelimme View-olioiden tapahtumankäsittelijöihin. Swingissä voimme tehdä suurin piirtein samoin.

Tehdään Swing-käyttöliittymä, joka näyttää samalta kuin edellisetkin esimerkkimme, mutta reagoi napinpainalluksiin näin:

  1. Se selvittää, mistä nappulasta tapahtuma on peräisin ja raportoi asian dialogi-ikkunassa näin:

    ../_images/gui6-fi.png
  2. Se päivittää tapahtuman lähteenä olleen nappulan tekstiä lisäten sen perään yhden huutomerkin.

Tarkastellaan ratkaisua ensin pseudokoodina ja sitten konkreettisena Scala-koodina.

Tapahtumankäsittelyä pseudokoodina

object Tapahtumakokeilu extends SimpleSwingApplication:

  val ekaNappi = Button("Paina minua")( () )
  val tokaNappi = Button("Ei kun MINUA!")( () )
  val kehote = Label("Paina jompaakumpaa napeista.")

  val kaikkiJutut = BoxPanel(Orientation.Vertical)
  kaikkiJutut.contents ++= Vector(kehote, ekaNappi, tokaNappi)
  val nappulaikkuna = MainFrame()
  nappulaikkuna.contents = kaikkiJutut
  nappulaikkuna.title = "Kokeiluikkuna"

  // Tapahtumat:

  Määrää, että tämä Tapahtumakokeilu-olio vastaanottaa ilmoitukset, kun nappulaa
  ekaNappi tai tokaNappi painetaan.

  Määrää, että kun tämä olio saa ilmoituksen napinpainalluksesta, se tuo näkyviin
  apuikkunan, jossa on viesti "Painoit nappia, jossa lukee: [painetun napin teksti]".
  Tämän jälkeen se lisää painetun nappulan tekstin perään huutomerkin.

  def top = this.nappulaikkuna

end Tapahtumakokeilu

Määrätään olio nappuloiden tapahtumankuuntelijaksi. Tästä eteenpäin se ikään kuin pitää korvat höröllään ja suorittaa tietyn koodin sitten, kun näitä nappuloita painetaan.

Tapahtumankuuntelijalle määritellään tapahtumankäsittelijä, jonka se suorittaa, kun se kuulee uudesta tapahtumasta.

Tarkennetaan tuota pseudokoodia:

  Määrää tämä Tapahtumakokeilu-olio ekaNappi-nappulan kuuntelijaksi.
  Määrää tämä Tapahtumakokeilu-olio tokaNappi-nappulan kuuntelijaksi.

  Lisää Tapahtumakokeilu-oliolle tapahtumankäsittelijä, joka toimii nappulaa painettaessa näin:
    1. Poimi paikalliseen muuttujaan painallus viittaus tapahtumaa kuvaavaan olioon.
    2. Selvitä tapahtumaoliolta kysymällä tapahtuman lähde eli nappula, jota painettiin.
    3. Selvitä lähdenappulan teksti nappulaoliolta kysymällä.
    4. Kutsu Dialog.showMessage, viestinä "Painoit nappia, jossa lukee: [teksti]".
    5. Muodosta uusi huutomerkillä kasvatettu teksti ja anna se lähdenappulalle uudeksi tekstiksi.

Tapahtumankäsittelyä konkreettisena Scalana

import scala.swing.*
import scala.swing.event.*

object Tapahtumakokeilu extends SimpleSwingApplication:
  // Muuten kuten yllä: ...

  this.listenTo(ekaNappi, tokaNappi)
  this.reactions += {
    case painallus: ButtonClicked =>
      val lahdenappula = painallus.source
      val viesti = "Painoit nappia, jossa lukee: " + lahdenappula.text
      Dialog.showMessage(kaikkiJutut, viesti, "Viesti")
      lahdenappula.text = lahdenappula.text + "!"
  }
end Tapahtumakokeilu

Tapahtumankäsittelyyn tarvitsemme pakkauksen scala.swing.event sisältöä.

listenTo-metodi määrää olion ilmoittautumaan kuuntelijaksi parametrin ilmoittamalle tapahtumien lähteelle. (Parametreja voi antaa useita; tässä annamme kaksi.)

reactions viittaa kokoelmaan tapahtumankäsittelijöitä. Tässä siihen lisätään yksi tapahtumankäsittelijä...

... joka määritellään aaltosulkeiden sisällä.

Käytämme match-käskyistäkin tuttua case-avainsanaa.

ButtonClicked on scala.swing.event-pakkauksen luokka, joka kuvaa nappulanpainalluksia. painallus puolestaan on tässä ohjelmoijan valitsema nimi muuttujalle, johon tallentuu havaittu tapahtuma (sovellusohjelmoijan näkökulmasta automaattisesti; vrt. match-käsky ja metodiparametrit). Muuttujan arvona on viittaus ButtonClicked-tyyppiseen olioon.

Koko rivi siis tarkoittaa: "Tapauksessa, jossa havaittu tapahtuma on tyyppiä ButtonClicked, ota viittaus tapahtumaolioon talteen muuttujaan nimeltä painallus ja suorita seuraavat koodirivit:"

Tapahtumaolion source-muuttuja kertoo tapahtuman lähteen eli tässä painetun nappulan. Nappulaolion text-muuttujasta saadaan nappulan sisältämä merkkijono. textille voi myös sijoittaa uuden arvon.

Koodi löytyy myös SwingExamples-moduulin tiedostosta EventTestApp.scala. Voit itse muokkailla ohjelmaa ja kokeilla mitä tapahtuu.

Tapahtumankäsittelijöistä Scalassa

Swing-ohjelmassa on muitakin tapoja määritellä tapahtumankäsittelijöitä. Tässä esitetty tapa on kurssin tarjoamien esitietojen valossa yksinkertaisin ja siksi valittu tähän.

Muista tapahtumatyypeistä

scala.swing.event-pakkauksessa on luokkaa ButtonClicked vastaavia luokkia myös muunlaisille käyttöliittymätapahtumille. On olemassa esimerkiksi luokat MouseClicked, MouseWheelMoved, MouseMoved, KeyTyped jne. Kahdesta ensin mainitusta on esimerkit Llama-ohjelmassa, johon palaamme hetimmiten.

Laama

Et toki lintsannut

Kokeilithan jo Llama-sovellusta kuten luvun alussa pyydettiin? Seuraava teksti voi olla kovin vaikeaselkoista, jos et kokeillut.

Aiemmista luvuista tiedät, että sovellusohjelman keskeisiä osia ovat käyttöliittymä ja sisäinen malli. Käyttöliittymä tulkitsee käyttäjän komentoja ja voi muuttaa sisäistä mallia komentojen mukaisesti.

Aloitetaan laamaohjelman tarkastelu sen sisäisestä mallista eli laaman toimintalogiikasta.

Llama-luokka

Logiikka on toteutettu luokaksi Llama. Sen toteutusta ei käydä tässä läpi, vaan opettelemme ainoastaan rajapinnan:

  • Llama-luokan ilmentymä kuvaa laamaa, jolla on muuttuva tila.

  • Parametrittomilla metodeilla tickle, poke ja slap laamaa voi rapsuttaa, tökätä tai inhottavan käyttäjän ollessa kyseessä läpsäyttää.

  • Laamalla on kärsivällisyysarvo, joka alkaa sadasta prosentista. Kun laamaa käsitellään, se kiihtyy sadasta nollaan varsin rivakasti.

  • Parametriton isOutOfPatience-metodi kertoo, onko laaman kärsivällisyys aivan lopussa.

  • Laamaolion stateOfMind-metodi — parametriton sekin — palauttaa merkkijonon, joka kuvaa laamaolion mielipidettä asiaintilasta.

Käyttöliittymän komponentit

Llama-sovelluksen käyttöliittymän siis kuuluisi luoda Llama-olio sekä tarjota graafiset komponentit, joilla voi

  • näyttää laamaa esittävän kuvan ja sen yläpuolella laaman mielentilaa kuvaavan tekstin

  • tarjota käyttäjälle mahdollisuuden rapsuttaa (hiiren rullalla), tökätä (hiiren napilla) tai läpsäistä (tuplaklikkauksella) laamaa

  • luoda uuden laaman painamalla Again!.

Seuraava koodi luo ja asemoi käyttöliittymän osat, mutta ei vielä reagoi tapahtumiin:

object LlamaApp extends SimpleSwingApplication:
  // A couple of images:
  val alivePic = ImageIcon(this.getClass.getResource("pics/alive.jpg"))
  val deadPic  = ImageIcon(this.getClass.getResource("pics/dead.jpg"))

  // Access to the model (internal logic of the app):
  var targetLlama = Llama()

  // Components:
  val commentary = Label()
  val pictureLabel = Label()
  val startOverButton = Button("Again!")( () )
  this.updateLlamaView()

  // Layout:
  val verticalPanel = BoxPanel(Orientation.Vertical)
  verticalPanel.contents ++= Vector(commentary, pictureLabel, startOverButton)
  val llamaWindow = MainFrame()
  llamaWindow.title = "A Llama"
  llamaWindow.resizable = false
  llamaWindow.contents = verticalPanel

  def top = this.llamaWindow

  private def updateLlamaView() =
    this.commentary.text = targetLlama.stateOfMind
    this.pictureLabel.icon = if this.targetLlama.isOutOfPatience then this.deadPic
                                                                 else this.alivePic
end LlamaApp

LlamaApp-olio lataa aluksi pakkauksen sisältämistä tiedostoista pari kuvaa ImageIcon-olioiksi.

Käyttöliittymän on voitava käsitellä ohjelman sisäistä mallia. Mallin muodostaa tässä yksi laamaolio (kerrallaan).

Käyttöliittymässä on pari Labelia (teksti ja kuva) sekä yksi nappula.

Ne pannaan allekkain paneeliin ja paneeli ikkunan sisällöksi.

resizable-muuttujan asettaminen falseksi lukitsee ikkunan koon. Käyttäjä ei voi muuttaa kokoa esimerkiksi ikkunan reunoja siirtämällä.

Apumetodi updateLlamaView selvittää laaman nykytilan perusteella näytettävän kommentin ja kuvan ja päivittää Labeleita sen mukaisesti.

Hiiren ja nappulan kuunteleminen

Lisäämällä seuraavat rivit LlamaApp-sovellukseen saamme ohjelman toimimaan halutusti.

this.listenTo(startOverButton)
this.listenTo(pictureLabel.mouse.clicks, pictureLabel.mouse.wheel)
this.reactions += {
  case moveEvent: MouseWheelMoved =>
    targetLlama.scratch()
    updateLlamaView()

  case clickEvent: MouseClicked =>
    if clickEvent.clicks > 1 then     // double-click (or triple, etc.)
      targetLlama.slap()
    else
      targetLlama.poke()
    updateLlamaView()

  case clickEvent: ButtonClicked =>
    targetLlama = Llama()
    updateLlamaView()
}

LlamaApp-olio kuuntelee paitsi nappulaa myös hiiren käyttöä. Se järjestyy näin.

Lisätään kolme eri tapausta: hiiren rullan liike, hiiren klikkaus ja GUI-nappulan painallus. Kullekin tapaukselle on erillinen suoritettava tapahtumankäsittelijäkoodi.

Kutsumme ylempänä määriteltyä apumetodia aina käyttöliittymätapahtuman jälkeen, jotta näkymä päivittyisi laaman käsittelyn perusteella.

Toinen esimerkki: RandomText

Siirrytään toisen sovelluksen pariin. Käytetään pakkausta o1.randomtext ja sen luokkaa RandomTextGenerator. Tuon luokan ilmentymät ovat kevyeen viihdekäyttöön sopivia "puppusanageneraattoreita".

Tällaisella oliolla on metodi randomize, jolle annetaan parametriksi tiedoston nimi tai verkko-URL. Metodi palauttaa satunnaisesti tuotettua tekstiä, joka muistuttaa alkuperäistekstiä. (Näet esimerkin, kunhan ohjelma saadaan toimimaan.)

Tähdätään siihen, että ohjelmassa olisi tämännäköinen graafinen käyttöliittymä:

../_images/gui7.png

Meillä on kaksi uutta haastetta:

  1. Miten saamme näkyviin tekstikentän (text field) syötettä varten ja monirivisen tekstialueen (text area), johon ohjelman syytämä puppu ilmestyy käyttäjän painaessa Randomize!-nappulaa?

  2. Miten saamme komponentit asemoitua niin, että kehote, tekstikenttä ja nappula ovat ylärivillä ja tekstialue täyttää loput ikkunasta?

Aloitetaan jälkimmäisestä. Ratkaisu on sama kuin aiemminkin: paneelit. Ikkunassa on käytetty kahta paneelia:

Ikkunan sisältönä on tässäkin BoxPanel. Siinä ovat allekkain "ylärivi" sekä iso tekstialue (TextArea).

Ylärivi on paneeli sekin. Se sisältää vierekkäin asemoituina tekstinpätkän (Label), tekstinsyöttökentän (TextField) sekä nappulan (Button). Tämä paneeli on tarkemmin sanoen tyyppiä FlowPanel. Olisi myös voitu tehdä vaakasuora BoxPanel, mutta...

... sitten tulos olisi ollut tällainen. BoxPanelin ominaisuuksiin kuuluu sen sisällön venyttäminen reunoihin asti, mikä näyttää tässä vähemmän kivalta.

Seuraava Scala-koodi asettelee komponentit kuvattuun tapaan.

object RandomTextApp extends SimpleSwingApplication:
  // Components:
  val prompt = Label("Source file or URL:")
  val sourceField = TextField("alice.txt", 50)
  val randomizeButton = Button("Randomize!")( () )
  val outputArea = TextArea("Random stuff will appear here.", 30, 85)
  outputArea.editable = false
  outputArea.lineWrap = true

  // Layout:
  val topRow = FlowPanel()
  topRow.contents ++= Vector(prompt, sourceField, randomizeButton)
  val wholeLayout = BoxPanel(Orientation.Vertical)
  wholeLayout.contents ++= Vector(topRow, outputArea)

  val window = MainFrame
  window.title = "Random Text Generator"
  window.resizable = false
  window.contents = wholeLayout

  def top = this.window
end RandomTextApp

Huomaa luokkien TextField ja TextArea käyttö. Luontiparametrit alustavat näiden komponenttien sisällön ja määräävät niiden mitat (sarakkeina ja tekstialueen tapauksessa myös riveinä).

Haluamme, että tietokone tuottaa outputArean sisällön. Asettamalla editable-ominaisuuden arvon falseksi estämme käyttäjää muokkaamasta tekstiä. lineWrap-ominaisuudella puolestaan saadaan komponentti jakamaan pitkä merkkijono usealle riville.

FlowPanel tuottaa tässä hieman kauniimman lopputuloksen kuin BoxPanel.

BoxPaneliakin on tässä käytetty. Sillä sijoitamme yläriviä kuvaavan paneelin tekstialueen yläpuolelle.

Tämä toteutus löytyy SwingExamples-moduulin pakkauksesta o1.randomtext.

Mutta siitähän tuli ruma?

Kun kokeilet ajaa moduulista SwingExamples löytyvän ohjelman, niin saatat huomata, ettei se tuota ihan sen näköistä käyttöliittymää kuin yllä kuvassa. Osaset ovat kyllä oikeassa järjestyksessä, mutta nappulan ja muiden komponenttien ilmiasu on ikävällä tavalla "retron" näköinen eikä sellainen kuin se muilla ikkunoilla ja nappuloilla yleensä on siinä ympäristössä, jossa ajat ohjelmaa. Miksi?

Asian taustalla on se, että eri käyttöympäristöissä ikkunat ja muut käyttöliittymäkomponentit näyttävät pääpiirteissään samalta mutta silti selvästi erilaisilta. (Vertaa esimerkiksi, miltä graafiset käyttöliittymät näyttävät Windows- ja Mac-koneilla.) Sanotaan, että käyttöliittymillä on eri ympäristöissä eri look and feel.

Swing-käyttöliittymäkirjasto tarjoaa mahdollisuuden käyttää erilaisia look and feel -asetuksia. Ellet toisin määrittele, käytetään Swingin oletusasetuksia (ns. cross-platform look and feel). Jos haluat käyttää muita asetuksia, on annettava erillinen käsky.

Muokataan RandomTextAppin alkua siten, että siinä lukee:

import javax.swing.UIManager

object RandomTextApp extends SimpleSwingApplication:
  UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName)
  // ...

UIManager tarjoaa ohjelman graafiseen ajoympäristöön liittyviä työkaluja. Tässä käytämme niitä...

... saadaksemme tiedon siitä, mikä on ohjelmaa senhetkisen ajoympäristön look and feel, sekä...

... määräämään tämä look and feel käyttöön.

Jos lisäät nuo käskyt RandomTextAppiin, niin se näyttää ajettaessa siltä kuin sopii käyttöjärjestelmässäsi olettaakin. Muokattu ohjelma siis ei käytä Swingin oletusasetuksia vaan mukautuu kulloiseenkin ajoympäristöönsä.

Ohjelmointitehtävä: RandomTextApp

Täydennä RandomTextApp sellaiseksi, että se toimii koodiin kirjoitettujen kommenttien mukaisesti. Tarkemmin sanoen:

  1. Kokeile annettua versiota RandomTextApp-sovelluksesta. Käyttöliittymän palaset ovat kohdallaan, mutta sovellus ei vielä tee mitään.

  2. Ohjelman tulisi suoltaa "pupputekstiä" käyttäjän painaessa nappulaa. Tekstin tuottamisesta huolehtii ohjelman sisäisenä mallina toimiva RandomTextGenerator-olio. Liitä RandomTextApp-olioon viittaus luokan RandomTextGenerator ilmentymään.

    • Vrt. LlamaAppista oli viittaus Llama-olioon yllä.

    • Anna luontiparametriksi kokonaisluku 9. Pupputekstigeneraattorin luontiparametrien merkitys on selitetty koodin kommenteissa. Voit kyllä kokeilla omin päin muitakin parametriarvoja.

  3. Kirjoita parametriton funktio, joka tekee sen, mitä nappulaa painaessa kuuluu tapahtua. Voit nimetä funktion itse (esim. onRandomizeClick). Sen pitää toimia näin:

    • Tuota pupputeksti kutsumalla generaattoriolion randomize-metodia. Anna parametriksi tekstikentän sourceField sisältämä merkkijono eli tekstikentän text-ominaisuuden arvo.

    • Aseta paluuarvona saatu puppu outputArean sisällöksi sijoittamalla se text-ominaisuuden arvoksi.

  4. Määrää ohjelma reagoimaan napinpainalluksiin (ButtonClicked) kutsumalla edellisessä kohdassa laatimaasi funktiota. Kokeile jompaakumpaa tai molempia yllä opetetuista menetelmistä:

    • Aseta RandomTextApp-olio kuuntelemaan napinpainalluksia (listenTo). Lisää RandomTextApp-olion reactions-luetteloon tapahtumankäsittelijäkoodi, joka kutsuu funktiotasi.

    • Tai yksinkertaisemmin: kirjaa haluttu funktiokutsu suoraan Button-olion luovaan käskyyn.

  5. Kokeile, miten ohjelma toimii. Jos kaikki on mennyt hyvin, Randomize!-nappulan painaminen tuottaa nyt sisältöä tekstialueelle.

  6. Koska tekstialueen lineWrap-ominaisuus asetettiin yllä trueksi, tuotettu pupputeksti jakautuu riveiksi. Rivit kuitenkin katkeavat myös sanojen keskeltä, mikä ei näytä edustavalta. Korjaa asia laittamalla tekstialueen wordWrap-ominaisuus "päälle" samaan tapaan.

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

Käyttöliittymät jäävuoren huippuna

Joel Spolsky, seurattu blogisti ja toinen Stack Overflow -neuvontasivuston perustajista, kirjoitti vuonna 2002 kaupallisiin ohjelmistohankkeisiin liittyvästä "jäävuorisalaisuudesta". Tämä salatieto on nyt ulottuvillasi, kun olet oppinut ohjelmoinnista ja käyttöliittymistä.

You know how an iceberg is 90% underwater? Well, most software is like that too — there’s a pretty user interface that takes about 10% of the work, and then 90% of the programming work is under the covers.

That’s not the secret. The secret is that People Who Aren’t Programmers Do Not Understand This.

There are some very, very important corollaries to the Iceberg Secret.

Important Corollary One. If you show a nonprogrammer a screen which has a user interface that is 90% worse, they will think that the program is 90% worse.

Important Corollary Two. If you show a nonprogrammer a screen which has a user interface which is 100% beautiful, they will think the program is almost done.

Osin tämänkin vuoksi on hyvä, että yhä useampi kansalainen ymmärtää edes vähän ohjelmoinnista.

Yhteenvetoa

  • Sovelluksen graafinen käyttöliittymä eli GUI rakennetaan apukirjastoa käyttäen. Yksi tällainen kirjasto on Scalan Swing.

  • Moni GUI-kirjasto tarjoaa käyttöliittymäkomponentteja kuten ikkunoita, nappuloita ja tekstikenttiä. Niin Swingkin. Swing on oliopohjainen ja kuvaa nämä käsitteet luokkina.

  • Kun komponentteja asemoidaan GUI-ikkunaan on usein hyödyllistä käyttää paneeleiksi kutsuttuja apukomponentteja, joihin muita komponentteja voi ryhmitellä.

  • GUI voi reagoida käyttäjän toimiin eli käyttöliittymätapahtumiin määräämällä olion toimimaan ns. tapahtumankuuntelijana.

    • Esimerkiksi nappulaolio voi välittää kuuntelijoilleen tiedon siitä, kun sitä painetaan.

    • Saadessaan näin tiedon tapahtumasta kuuntelija suorittaa tapahtumankäsittelykoodin, joka määrää, mitä esimerkiksi nappulanpainalluksesta kyseisessä sovelluksessa seuraa.

  • Lukuun liittyviä termejä sanastosivulla: graafinen käyttöliittymä GUI; käyttöliittymätapahtuma, tapahtumankuuntelija, tapahtumankäsittelijä; Swing.

Tahtoo lisää!

Viralliset GUI-oppimistavoitteet ovat tällä kurssilla vaatimattomat. Aihetta ei ole painotettu kurssilla, koska GUI-ohjelmointiin tarvitaan melko paljon sellaistakin osaamista, joka ei oikein tue ohjelmoinnin yleisten peruskäsitteiden oppimista ja joka pakottaa menemään tietyn GUI-kirjaston yksityiskohtiin.

Voit opetella lisää GUI-ohjelmoinnista ja Swing-kirjastosta omin päin sekä kurssilla Ohjelmointistudio 2.

Löydät lisäesimerkkejä myös monesta kurssin oheismoduulista, jossa on valmiina annettu graafinen käyttöliittymä. Näissä moduuleissa käytetään runsaasti tekniikoita, joita ei tässä luvussa käsitelty (esim. omien elementtityyppien periyttämistä Swing-kirjaston valmiista elementtityypeistä).

Palaute

Huomaathan, että tämä on henkilökohtainen osio! Vaikka olisit tehnyt lukuun liittyvät tehtävät parin kanssa, täytä palautelomake itse.

Tekijät

Tämän oppimateriaalin kehitystyössä on käytetty apuna tuhansilta opiskelijoilta kerättyä palautetta. Kiitos!

Materiaalin luvut tehtävineen ja viikkokoosteineen on laatinut Juha Sorva.

Liitesivut (sanasto, Scala-kooste, usein kysytyt kysymykset jne.) on kirjoittanut Juha Sorva sikäli kuin sivulla ei ole toisin mainittu.

Tehtävien automaattisen arvioinnin ovat toteuttaneet: (aakkosjärjestyksessä) Riku Autio, Nikolas Drosdek, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Joel Toppinen, Anna Valldeoriola Cardó ja Aleksi Vartiainen.

Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.

Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista suunnittelivat Juha Sorva ja Teemu Sirkiä. Teemu Sirkiä ja Riku Autio toteuttivat ne apunaan Teemun aiemmin rakentamat työkalut Jsvee ja Kelmu.

Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset laati Juha Sorva.

O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen, Juha Sorva ja Jaakko Nakaza. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.

Tapa, jolla käytämme O1Libraryn työkaluja (kuten Pic) yksinkertaiseen graafiseen ohjelmointiin, on saanut vaikutteita tekijöiden Flatt, Felleisen, Findler ja Krishnamurthi oppikirjasta How to Design Programs sekä Stephen Blochin oppikirjasta Picturing Programs.

Oppimisalusta A+ luotiin alun perin Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Nykyään tätä avoimen lähdekoodin projektia kehittää Tietotekniikan laitoksen opetusteknologiatiimi ja tarjoaa palveluna laitoksen IT-tuki; sitä ovat kehittäneet kymmenet Aallon opiskelijat ja muut.

A+ Courses -lisäosa, joka tukee A+:aa ja O1-kurssia IntelliJ-ohjelmointiympäristössä, on toinen avoin projekti. Sen suunnitteluun ja toteutukseen on osallistunut useita opiskelijoita yhteistyössä O1-kurssin opettajien kanssa.

Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.

Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.

a drop of ink
Palautusta lähetetään...