Vaikka IT-alalla on siirrytty paljon web-käyttöliittymiin ja -järjestelmiin, on työpöytäsovelluksilla vielä paikkansa. Käyttöliittymien kehittämiseksi on muutamia varteenotettavia alustoja, muun muassa WPF ja Qt. JVM-puolella avuksi löytyvät Swing, SWT ja JavaFx. Halusin selvittää, löytyisikö minun mieluisimmalle kielelleni, Scalalle, riittävän hyvää vaihtoehtoa.
ScalaFx on DSL-wrapperi JavaFx:lle Scalalla. JavaFx on varsin valmis alusta, ja ScalaFx:ääkin on kehitetty vuodesta 2011. Kokeillakseni alustaa valitsin perinteistä ToDo-applikaatiota monimutkaisemman ohjelman, jotta joutuisin oikeasti perehtymään kirjaston ominaisuuksiin.
Sovellus
Kohteena on simulaattorin konfiguraatiokäyttöliittymä. Simulaation olisi tarkoitus tuottaa dataa oppivalle järjestelmälle. Data pyrkii mallintamaan sensoriverkon tuottamaa tietoa, esimerkiksi teollisuusympäristössä. Tarkoituksena on pystyä määrittelemään tiloja, suureita, sensoreita ja poikkeamia aikaväleille, ja käynnistää simulaatio, joka viestii järjestelmälle halutunlaista dataa. Alla on listattu komponentit, joista simulaatio koostuu:
- tilat
- määrittää ajanjakson, johon muut tietomallin osat voivat viitata
- määritetään tietylle aikavälille
- voidaan määrittää syklisiksi
- suureet
- suureelle määritetään oletuskäyttäytyminen funktiona ajan suhteen
- jokaiselle tilalle voidaan määrittää käyttäytyminen joko ylimäärittelevästi tai kumulatiivisesti
- suure voi toimia funktiona toiseen suureeseen
- sensorit
- mittaa tiettyä suuretta tietyin aikavälein
- voidaan määrittää ID, lähetyskohde yms.
- poikkeamat
- määritetään tietylle aikavälille
- valitaan, miten poikkeama vaikuttaa tiettyyn suureeseen tietyllä aikavälillä.
Tätä kirjoitusta varten toteutin vain osan käyttöliittymästä, sillä kokonaisuutena varsinainen applikaatio olisi liian suuri pelkkään evaluointitarkoitukseen. Luonnostelin käsin sovelluksen kokonaisuuden sekä simulaation visualisoinnin ennen evaluointia, kuvat alla.
Evaluointi
Lähestyin käyttöliittymän rakentamista seuraavasti:
- käyttöliittymä kuvana
- käyttöliittymä SceneBuilderilla
- käyttöliittymän koodit elementtien osalta
- kontrollilogiikkaa käyttöliittymäelementeille.
Jotta sain paremman kuvan siitä, mitä layout- ja käyttöliittymäkomponenttia käyttäisin, loin SceneBuilderilla fxml-muotoisen leiskan. SceneBuilder on JavaFx:lle kehitetty visuaalinen leiskan generointityökalu. Työkalun listoilta oli helppo tarkastella valmiina olevia komponentteja, ja nopeasti testailla eri yhdistelmien toimivuutta. Koodin puolella oli helppoa lisäillä palaset kohdilleen, kun sen oli kertaalleen tehnyt visuaalisella työkalulla.
Alla esimerkki päänäkymän luomisesta. Komponentit tässä demossa oli kaikki määritetty Widgets Objectin alla.
Main:
new BorderPane {
this.top = new VBox {
this.children = Seq(
widgets.menu,
widgets.toolbar
)
}
this.left = widgets.componentsPanel
this.center = new StackPane {
this.styleClass add "chart-panel"
this.alignment = Pos.Center
this.children = Seq(
widgets.chart
)
}
this.bottom = new VBox {
resizable = true
fillWidth = true
children = Seq(
widgets.playbackCanvas,
widgets.playControls
)
}
}
Widgets:
val controlPanel = new HBox {
styleClass = Seq("control-panel")
children = Seq(
tree,
formPane
)
}
val tree = new TreeView[SimulationComponent] {
styleClass = Seq("tree-panel")
showRoot = true
root = new TreeItem[SimulationComponent](
new SimulationComponent("Simulation Example",Option.empty)
) {
expanded = true
children = ObservableBuffer(
states,
qualities,
sensors,
deviations
)
}
}
Aikajana-komponentti vaati oman renderöintinsä. Tähän tarkoitukseen löytyy kirjastosta Canvas-elementti, johon saa grafiikkakontekstin.
val gc = graphicsContext2D
gc.fill = Color.web("FB9696FF")
gc.fillRect(x, y, w, h)
Toiminnallisuudet, joita ei olla otettu huomioon kirjastossa, vaativat Java-tyylisiä kytköksiä. Esimerkiksi applikaation PrimaryStagen leveydelle jouduin asettamaan Java-tyylisesti alla olevan koodin, jotta canvaksen saa muuttamaan kokoaan ikkunan koon muuttuessa.
width.addListener(new ChangeListener[Number] {
override def changed(
observable: ObservableValue[_ <: Number],
oldValue: Number,
newValue: Number
): Unit = {
widgets.playbackCanvas.width = newValue.doubleValue()
widgets.playbackCanvas.redraw()
}
})
Alla on kuvankaappaus sovelluksesta.
Päätelmät
Toteutukseen meni aikaa 6 h, suunnitteluun 1 h. Suurin osa ajasta meni dokumentaation hakemiseen JavaFx:n puolelta tai esimerkkien kaiveluun ScalaFx:n tarjoamista demoista.
Perustoiminnallisuuden kirjoittaminen sujuu nopeasti, ja koodi itsessään on varsin kompaktia ja luettavaa. Komponentit ovat suurilta osin intuitiivisia ja toimivia. Osassa esiintyy odottamattomia ongelmia, muun muassa Comboboxin sisällön lisääminen dynaamisesti tuntui rikkovan sovelluksen ainakin kyseisellä versiolla. Asettelu onnistuu helposti, ja suuri osa komponenteista on suunniteltu skaalautuviksi.
Dokumentaatio ScalaFX:ssä on erittäin puutteellinen. Virallinen dokumentaatio ja demot ovat pintapuoliset. Käyttö tuntuisi vaativan perusteellisen tuntemuksen JavaFX:stä, jolloin pystyisi päättelemään tavan, jolla komponentti on saatu toiminnalliseksi Scala-puolella.
Kaikissa osuuksissa vastaan tuli se, että kirjasto on suunniteltu oliomalliseen kehitykseen. Iso osa muuttumattoman datan hyödyistä jää siis käyttämättä. Mieluummin kirjoittaisin käyttöliittymäosuuden Javalla ja yhteistoiminnalisuuden Scalan tarjoaman rajapinnan taakse. Kuten niin monen ”wrapperin” kanssa, lähinnä jää maku, että mieluummin käyttäisi kirjastoa alkuperäisessä ekosysteemissään.
En lähtisi käyttämään ScalaFX:ää sellaisenaan nykyisellä tietotasollani. Vaativampi käyttö vaatisi vähintään syvällisemmän tietämyksen JavaFX:stä, ja silloinkin avoimeksi jää, etteikö olisi helpompaa kirjoittaa UI-puoli suoraan Javalla.
Resursseja
- ScalaFx
- JavaFx
- SceneBuilder