Muutettu viimeksi 26.4.2016 / Sivu luotu 26.4.2010 / [oppikirjan esimerkit] / [Scala]
Sivun sisältöä:
Tässä luvussa luodaan pikakatsaus graafisen käyttöliittymän toteuttamisen vaivattomuuteen Scalalla – vaivatonta se on ainakin Javaan kauheuteen verrattuna. Monet Javassa kömpelöt ilmaukset on saatu luonnollistettua Scalan funktioparametrien ja operaattorimerkeiltä näyttävien metodikutsujen ansiosta. Eikä lopulta ole lainkaan yhdentekevää, voiko ohjelmoija ajatella selkein ja luontevin käsittein!
import scala.swing._ object FirstSwingApp extends SimpleSwingApplication { def top = new MainFrame { title = "First Swing App" contents = new Button { text = "Click me" } } }Kuten näkyy, MainFrame-luokassa on mukavia muuttujia eli kenttiä, joille voi antaa arvoja! Sama tekniikka löytyy myös luokasta Button. Hyvä idea!
MainFrame myös sisältää pääohjelman, joka mm. kutsuu top-metodia. MainFrame on sellainen Frame, joka osaa päättyessään lopettaa koko graafisen sovelluksen.
import scala.swing._ object SecondSwingApp extends SimpleSwingApplication { def top = new MainFrame { title = "Second Swing App" val button = new Button { text = "Click me" } val label = new Label { text = "No button clicks registered" } contents = new BoxPanel(Orientation.Vertical) { contents += button contents += label border = Swing.EmptyBorder(30, 30, 10, 30) } } }Taas tätä Scala tyylikkyyttä: contents-muuttujaan lisätään nappula ja teksti lisäysoperaatiolla "+=".
Huom: Tuossa BoxPanelin luonnissa on taas yksi Scalan uutuus: new-ilmauksen yhteydessä muutetaan luotavan olioin kenttien arvoja. Pienempi esimerkki: Hmm.sca.
Tapahtumia kuunnellaan (listenTo(source)) ja niihin reagoidaan (reactions += ...):
import scala.swing._ import scala.swing.event._ object ReactiveSwingApp extends SimpleSwingApplication { def top = new MainFrame { title = "Reactive Swing App" val button = new Button { text = "Click me" } val label = new Label { text = "No button clicks registered" } contents = new BoxPanel(Orientation.Vertical) { contents += button contents += label border = Swing.EmptyBorder(30, 30, 10, 30) } listenTo(button) var nClicks = 0 reactions += { case ButtonClicked(b) => nClicks += 1 label.text = "Number of button clicks: "+ nClicks b.text = nClicks +". time. Again?" } } }Kuuntelu voidaan myös lopettaa (deafTo(source)). Tapahtumista saadaan case-class-oliota (ei käsitelty kurssilla!), joihin reagointi eli tapahtuman käsittely lisätään top-kehyksen reactions-ominaisuuteen. Tuo b tuolla ButtonClicked(b)ssä antaa käyttöön AbstractButton-tyyppisen komponentin, jolle tapahtui jotakin ja jota voidaan toki myös itse käyttää.
Yhdessä lisättävässä reaktiossa saa olla useampiakin case-osia. Sekä itse lisätyistä että perityistä case-osista suoritetaan kaikki sopivat järjestyksessä: viimeisimmäksi lisätty, toiseksi viimeisimmäksi lisätty, jne. Regoinnit siis viedään pinoon.
Reagointeja voi myös poistaa reactions-kokoelmasta. Ja sehän tietenkin tehdään vähennysoperaatiolla -=.
import swing._ import event._ object TempConverter extends SimpleSwingApplication { def top = new MainFrame { title = "Celsius/Fahrenheit Converter" object celsius extends TextField { columns = 5 } object fahrenheit extends TextField { columns = 5 } contents = new FlowPanel { contents += celsius contents += new Label(" Celsius = ") contents += fahrenheit contents += new Label(" Fahrenheit") border = Swing.EmptyBorder(15, 10, 10, 10) } listenTo(celsius, fahrenheit) reactions += { case EditDone(`fahrenheit`) => // !! val f = fahrenheit.text.toInt val c = (f - 32) * 5 / 9 celsius.text = c.toString case EditDone(`celsius`) => // !! val c = celsius.text.toInt val f = c * 9 / 5 + 32 fahrenheit.text = f.toString } } }
Huom: EditDone tulkitsee parametrinsa uudelleen määritellyksi, ellei sitä kirjoiteta noihin takaperoisiin aksenttimerkkeihin. Tekstikenttäoliot annetaan siis "literal identifier" -muodossa, jotta kääntäjä ymmärtäisi noiden tunnusten tarkoittavan sovelluksen omia kenttiä...
Toinen keino hoitaa asia on nimetä nuo tekstikenttäoliot alkamaan isolla (!!) kirjaimella:
import swing._ import event._ object TempConverter extends SimpleSwingApplication { def top = new MainFrame { title = "Celsius/Fahrenheit Converter" object Celsius extends TextField { columns = 5 } // !! object Fahrenheit extends TextField { columns = 5 } // !! contents = new FlowPanel { contents += Celsius contents += new Label(" Celsius = ") contents += Fahrenheit contents += new Label(" Fahrenheit") border = Swing.EmptyBorder(15, 10, 10, 10) } listenTo(Celsius, Fahrenheit) reactions += { case EditDone(Fahrenheit) => val f = Fahrenheit.text.toInt val c = (f - 32) * 5 / 9 Celsius.text = c.toString case EditDone(Celsius) => val c = Celsius.text.toInt val f = c * 9 / 5 + 32 Fahrenheit.text = f.toString } } }