A fejezetet Kovács Vincent írta a Scala Swing Design dokumentum alapján.
A scala.swing csomag a JAVA Swing csomagon alapszik. A csomag segít
abban, hogy a Swing használata átláthatóbb és Scala „barát” legyen. A következő programkód tekinthető a scala.swing
"Hello Word!"-jének:
Az „egyszerű” Swing alkalmazás tartalmaz egy main objektumot, mint a fenti HelloWorld
mintaalkalmazás is, ami a SimpleSwingApplication osztályból származik. Az alkalmazásnak
implementálnia kell a top metódust, amelyben egy paraméternélküli Frame-t kell
visszaadni. Ebben az esetben ez a MainFrame, ami a Frame osztály egyik leszármazottja.
A Frame egy standard operációs rendszeri ablak, ami tartalmazza a minimalizáló,
maximalizáló és bezáró gombokat is. A MainFrame bezáráskor automatikusan terminál maga az alkalmazás
is.
Indításkor a SimpleSwingApplication gondoskodik a Swing keretrendszer
megfelelő inicializálásáról és megnyitja a top metódus által visszaadott Frame-t. A fenti implementáció
létrehoz egy fő keretet egy anonim osztálydefiníció segítségével:
Az anonim osztály definíció megengedi, hogy a MainFrame összes változóját elérjük a „kapcsos”
zárójeleken belül. Elsősorban beállításra kerül a keret címe (title paraméter), ami egy
"Hello, World!" szöveg lesz, majd beállításra kerül a keretnek a tartalma (content
paraméter) is.
Sok függvény és osztály a scala.swing-ben hasonlóképpen épül fel, mint a Java-ban.
Például egy új gomb létrehozásához elég csak annyit írni, hogy:
Ha nagyon sok paramétert szeretnénk megadni, akkor ajánlott az anonim osztálydefiníciót alkalmazni, ami a kódot sokkalta olvashatóbbá teszi.
A SimpleSwingApplication osztály a SwingApllication osztály leszármazottja, ami
implementál egy alapértelmezett main metódust. Ezen felül további metódusokat is implementál:
A startup metódus az alapértelmezett main metódus elején kerül
meghívásra és a kliens, vagyis a származtatott osztály, által kell implementálva lennie.
A quit metódus bezárja magát az alkalmazást.
A shutdown metódus a kliens által felül lehet definiálni, hogy az erőforrások a
megfelelőképpen kerüljenek felszabadításra az alkalmazás bezárásakor.
A SimpleSwingApplication egy alapértelmezett implementációt tartalmaz a startup
metódusra, ami megjeleníti a kliens által implementált top metódus által visszaadott
Frame-t.
Az előzőekben láthattuk, hogy egy gombot adtunk hozzá a fő ablakkerethez. Egy bonyolultabb megjelenítéssel
rendelkező alkalmazásnál ez már nem elég. Ebben az esetben szükségünk van az egyes grafikus elemeket
konténerekbe rendezni. A scala.swing-ben hasonlóan, mint a Java Swing-ben két fajta
konténer komponens van: a panes és a panel.
A Java Swing esetében a konténerek kettéválik az elrendezéstől. Ahhoz, hogy létrehozásra kerüljön
egy Java Swing konténer, létre kell hozni egy JPanel-t és egy hozzá tartozó
LayoutManager-t:
A konténerhez tartozó komponensek a különböző add metódusokkal lehet hozzáadni, amit a
java.awt.Component alaposztály tartalmazza:
A fenti példakódhoz egy új gombot a következő kóddal lehet hozzáadni:
A scala.swing-ben a Java Swing-gel ellentétben a konténerek és az elrendezések
egybeolvadnak.
A LayoutContainer egy trait, amelyből származtatni lehet a különböző
konténereket. Ehhez implementálni kell a következő absztrakt metódusokat:
constraintsFor metódus az aktuális konténerhez tartozó megszorításokat kérdezi
le.
areValid metódus fordítási időben ellenőrzi, hogy a megadott megszorítások
érvényesek-e és ha nem akkor egy opcionális hibaüzenetet ad.
add metódus egy új komponenst ad hozzá a konténerhez a hozzá tartozó
megszorításokkal együtt.
A scala.swing az eseményeket egy publisher-ek (esemény kiváltók) és
reactor-ok (esemény megfigyelők) felépítés alapján kezeli.
Minden scala.swing komponens egyben egy esemény megfigyelő, amely figyeli az eseményeket és egy
esemény kiváltó, amely kivált különböző eseményeket. A következő egyszerű kódrészlet bemutatja, hogyan lehet
hőmérsékleteket átváltani Celsiusról Fahrenheitre eseményeknek a segítéségével:
A fenti mintakódban látható, hogy a kerethez hozzáadódik két szövegmező, amiben a felhasználó megadhatja a kívánt
hőmérsékleteket (celsius, fahrenheit változók). A TextField objektumok az
EditDone eseményeket hirdetik meg, ha a szövegmezőn a szerkesztés befejezésre kerül. Ahhoz, hogy ezt az
eseményt el tudjuk kapni a reactions változóban található megfigyelőhöz kell hozzáadni az
szövegmezőket. A következő kódrészlettel kell kiegészíteni a programunkat:
Jelen esetben a reactions az alkalmazás globális megfigyelő objektuma.
Először a listenTo
metódussal feliratkozunk a szövegmezők eseményeire. (A listenTo a Reactor alaposztály
egyik
metódusa, amely a SwingApplication osztály egyik trait-ja.) A következő részben egyenként
megadásra kerülnek azok az események, amelyeket fel szeretnénk dolgozni, vagyis a fahrenheit és a celsius
paraméterekben tárolt szövegmezők EditDone eseményeire.
A feliratkozott események kiválasztását a scala.swing egyszerű mintaillesztéssel végzi el.
A scala.swing-ben mindent objektum egyedi, így a mintaillesztés könnyen működik. Ezzel szemben a
Java Swing-ben azesemények két szakaszban hajtódnak végre. Először az esemény típusa, plusz a hozzá
Listenermetódus.
A következő kódrészlet megmutatja, hogy a Java Swing-ben, hogyan kapunk el egy egyszerű egér
kattintást:
Ezzel ekvivalens kód a scala.swing-ben a következőképpen néz ki:
Hatékonysági okokból az egér eseményei nem a Component-ből származnak, hanem az egyik paraméteréből, a
mouse-ből, ezért először fel kell a listenTo metódussal erre iratkozni.
Az egész eseménykezelő rendszer a Publisher, Reactor és
a Reactions osztályokon alapszik. Az API használó felhasználók részére,
csak a Publisher osztályban a publish metódus az, ami érdekes. Ez értesíti a feliratkozott
reactor-okat:
Ahhoz hogy egy osztály kiválthasson eseményeket a Publisher osztályból kell származnia és a publish
metódus kell meghívnia az események kiváltásához.
A reactor-ok feliratkozhatnak a különböző eseményekre, illetve le is iratkozhatnak a
Reactor osztály következő metódusaival:
A Reactor osztálynak van egy különleges paramétere, amely tulajdonképpen a reaction-ok
gyűjteménye. Egy reaction egy Reactions.Reaction típusú objektum.
Új reaction-t a += metódussal lehet hozzáadni, törölni pedig a
-= metódussal lehetséges. Mindkettő metódus visszaadja a kapott reaction
objektumot:
A Java Swing egy javax.swing.Action interfészt biztosít, hogy az
eseményekre adott válaszok futtathatóak legyenek. Ezek különbeznek a Listener-ektől, mégpedig abban,
hogy információkat szolgáltatnak, amelyek későbbiekben hasznosak lehetnek. Ezeknek lehetnek gyakran használt
változók például az akció neve, gomb esetén maga a gomb felirata, vagy egy menü elem esetében a menünek a címe, vagy
az akcióhoz tartozó billentyűrövidítés, stb.
A scala.swing-ben erre az Action trait szolgál. A következő
beépített metódus nagyon kényelmessé teszi az akciók létrehozását:
Egy új akció létrehozásához csak elég annyit írni, hogy:
Egy komponenshez hozzárendelhető egy akció, amihez a Action.Triger trait-t
kell származtatnia. A követező kódrészlet bemutatja, hogy hogyan lehet egy gomb kattintásához egy akciót
hozzárendelni:
Mivel gyakori, hogy egy gombhoz akciókat rendeljünk hozzá, ezért van egy beépített gyár metódus is:
Ahhoz, hogy egy komponensnek megmondjuk, hogy nincs hozzárendelve egy akció az
ActionNoAction objektumot kell hozzárendelni, ami egyben az alapértelmezett érték is:
A lista és táblázat nézek komponensek feladata, hogy megadott mennyiség elemet egy egységes elrendezésbe helyezze. A lista elrendezések az adatok függőlegesen, vagy vízszintesen felsorolva jelenítik meg. A táblázat elrendezéseket a megadott adatokat egy táblázatos formában jelenítik meg. Opcionálisan megadható az oszlop és a sor fejlécek is.
A ListView osztálynak a konstruktora egy listát vár paraméterül, ami tartalmazza a
felsorolni kívánt
elemeket. Egy ListView a következőképpen hozható létre:
A ListView definiál egy selection változót, amelynek köszönhetően a kliens információt
nyerhet az aktuális kiválasztott elemről. Ennek megfelelően a fenti kódrészletet kicsit átalakítva:
Ezután lekérdezhető az aktuálisan kiválasztott City objektumot és azon belül is azoknak a nevei:
Ha fent megadott városokat megjelenítő ListView-t kirajzoljuk a Frame-re, akkor csak egy
összefűzött felsorolást fogunk kapni, valami hasonló formában:
A megjelenítő automatikusan az összes elemre meghívja a toString metódust. Tehát, ha egy
City-n meghívjuk a toString metódust, akkor az automatikusan visszaadja a konstruktor
paraméterein szereplő változókat toString formában.
A ListView osztály példánya a ListView.Renderer osztályt használja az
egyes elemek kirajzolásához. Egy új megjelenítő definiálásval ezt a listát szebbé, lehet tenni:
A fenti megjelenítő, csak a neveket fogja kirajzolni.
A Table felépítése és a hozzá tartozó megjelenítők hasonlóak a ListView-hoz.
A Table osztály nem csak listák megjelenítését támogatja, de kétdimenziós tömbök megjelenítésére is
alkalmas. A táblázat egy eleme több mindent reprezentálhat, mint például, egy sort, oszlopot, vagy magát csak egy
cella elemet.
Az egyedi rajzolás a scala.swing-ben hasonló a Java Swing-hez. Ehhez elegendő a megfelelő
Component osztályt, vagy ennek egyik alosztályát példányosítani és a paintComponent
metódust felüldefiniálni.
Például egy vonal kirajzolása scala.swing-ben:
A super.paintComponent-t azért szükséges meghívni, hogy a háttér és a különböző dekorációk is
kirajzolásra kerüljenek. Ezután kirajzolunk egy szöveget és egy útvonalat, amikor a felhasználó húzza az egeret.
A Component osztályban a következő rajzoló metódusok vannak:
A paint metódust a Swing rajzoló mechanizmusa hívja meg, amikor az adott
komponenst ki akarja rajzolni. Az alapértelmezett paint metódus meghívja a másik három rajzoló
metódust.