Urobme si v Groovy RSS čítačku a ukážme si, ako možno veľmi rýchlo naplácať aplikáciu v duchu rapid prototyping, teda rýchlej výroby predbežných verzií aplikácií.
Budeme potrebovať:
- načítavať XML (RSS) z webu
- previesť XML objekty na civilizované doménové objekty (napr.
RssItem
) - zobraziť tieto doménové objekty v zozname
JList
Načítanie XML/RSS z webu
XmlSlurper
pomôže veľmi jednoducho:
def xmlSlurper = new XmlSlurper()
def rss = xmlSlurper.parse(url)
V objekte rss
sa objaví naparsované XML: je to objekt NodeChildren
, kde môžeme následne radostne používať bodkovú notáciu a tým behať po hierarchii. Zoznam všetkých <item>
-ov získame:
def items = rss.channel.item
Ak ich chceme preiterovať, môžme:
rss.channel.item.each {
println it
}
Previesť XML objekty na doménové objekty RssItem
)
Objekt rss.channel.item
je objekt typu NodeChildren
. Ten by bol skvelý, akurát, že sa nedá použiť v ovládacom prvku JList
: tento komponent totiž potrebuje buď pole alebo zoznam objektov a NodeChildren
zoznamom nie je.
Ale to nie je problém: vytvorme si normálnu doménovú triedu pre položku RSS kanála:
class RssItem {
def title
def description
def pubDate
String toString() {
return title
}
}
Prevod medzi XML objektom a regulárnym zoznamom bude jednoduchý: collect()
-neme (namapujeme) každý prvok a prevedieme ho na objekt RssItem
:
def List<RssItem> parse(String url) {
def xmlSlurper = new XmlSlurper()
def rss = xmlSlurper.parse(url)
rss.channel.item.collect {
new RssItem(title: it.title,
description: it.description,
pubDate: it.pubDate)
}
}
Zobraziť tieto doménové objekty v zozname JList
Na zobrazenie použijeme klasiku: jedno textové políčko JTextArea
a jeden zoznam JList
. Aby vyzeralo okno pekne, použijeme BorderLayout
, ktorý rozdelí okno na osem sektorov. Zoznam bude v hornom (severnom) sektore, čo znamená, že sa bude naťahovať do šírky, ale nie do výšky. Textové políčko bude zase v strednom sektore, čiže sa bude naťahovať do všetkých smerov.
Oba komponenty musíme hodiť do JScrollPane
, inak sa v nich nebude dať scrollovať: posúvadlá sa totiž bez neho nezobrazia.
Technický detail: aj keď by bolo logické vytvárať okno zhora nadol, musíme najprv vytvoriť textové políčko a až potom zoznam: v metóde obsluhy zmeny výberu položky sa potrebujeme odkázať na premennú textového políčka.
Všetko to postavíme cez skvelú pomocnú triedu SwingBuilder
, ktorá má kopu syntaktického cukru na budovanie UI. Napr. vnáraním kučeravoozátvorkovaných blokov definujeme hierarchiu komponentov, a v rámci neho využívame viacero konštruktoroidných metód: napr:
frame(show: true, size: [200, 200], pack: true)
…vráti nový objekt JFrame
, kde sa nastaví postupne setShow(true)
, setSize(new Dimension(200, 200))
a zavolá pack()
.
Ak si potrebujeme odložiť vytvorený komponent pre ďalšie použitie, môžeme si ho poznačiť do premennej:
textArea = textArea(
rows: 10,
lineWrap: true,
wrapStyleWord: true,
text: "???",
)
Všetky parametre možno nájsť v dokumentácii: nájdite si vhodný widget a prezrite zoznam atribútov. (Popravde, dokumentácia je niekedy zastaralá, tak je to často pokus-omyl, resp. chýbajú v nej veľmi užitočné atribúty: napr. items
pri zozname).
Ukážme si celý kód:
SwingBuilder.edtBuilder {
frame(show: true, size: [200, 200], pack: true) {
borderLayout()
scrollPane(constraints: BorderLayout.CENTER) {
textArea = textArea(
rows: 10,
lineWrap: true,
wrapStyleWord: true,
text: "???",
)
}
scrollPane(constraints: BorderLayout.NORTH) {
rssItemList = list(
items: parse(rss),
selectionMode: ListSelectionModel.SINGLE_SELECTION,
valueChanged: {
textArea.text = rssItemList.selectedValue.description
},
)
}
}
}
Všimnime si vlastnosti textového políčka:
rows
udáva počet riadkov (analógiaJTextArea#setRows())
lineWrap
zapne zalamovanie (analógiaJTextArea#setLineWrap())
wrapStyleWord
zapne zalamovanie (analógiaJTextArea#setWrapStyleWord())
text
nastaví defaultný text (analógiaJTextArea#setText())
A vlastnosti zoznamu:
items
je “tajná” vlastnosť: umožňuje zobrať zoznam/pole objektov, ktoré zobrazí (vo vnútri vytvorí model, ktorý naplní dátami). To je presne dôvod, prečo sme hore prevádzaliNodeChildren
na zoznam.selectionMode
je klasický indikátor, ktorý zapne či vypne multivýber položiek. U nás multivýber nedáva zmysel, preto ho vypneme. (analógiaJList#setSelectionMode())
Obsluha kliknutia do zoznamu
SwingBuilder
podporuje v mnohých komponentoch skrátenie obsluhy: namiesto ukecaných listenerov stačí prihodiť obslužnú closure a je to. Vybudovaný zoznam má na toto vlastnosť valueChanged
:
valueChanged: {
textArea.text = rssItemList.selectedValue.description
},
Všimnime si, ako pristupujeme k premenným textArea
aj rssItemList
, ktoré vypadli z konštruktoroidných metód.
Zároveň využívame vlastnosti Swingu: rssItemList
je JList
, ktorý má metódu getSelectedValue()
(tu zápis skrátime), čo je objekt z modelu, typu RssItem
, na ktorom zavoláme getDescription()
skráteným zápisom.
Sumarizácia
Hľa, celý kód:
import groovy.swing.SwingBuilder
import javax.swing.*
import javax.swing.event.*
import java.awt.BorderLayout
def rss = "http://rss.sme.sk/rss/rss.asp?id=frontpage"
class RssItem {
def title
def description
def pubDate
String toString() {
return title
}
}
def List<RssItem> parse(String url) {
def xmlSlurper = new XmlSlurper()
def rss = xmlSlurper.parse(url)
rss.channel.item.collect {
new RssItem(title: it.title,
description: it.description,
pubDate: it.pubDate)
}
}
SwingBuilder.edtBuilder {
frame(show: true, size: [200, 200], pack: true) {
borderLayout()
scrollPane(constraints: BorderLayout.CENTER) {
textArea = textArea(
rows: 10,
lineWrap: true,
wrapStyleWord: true,
text: "???",
)
}
scrollPane(constraints: BorderLayout.NORTH) {
list = list(
items: parse(rss),
selectionMode: ListSelectionModel.SINGLE_SELECTION,
valueChanged: {
textArea.text = list.selectedValue.description
},
)
}
}
}
Zákerný bug
Ak púšťate zdroják z Groovy konzoly, pozor na zákerný bug (GROOVY-4596). Okno JFrame
, ktoré zavriete, neukončí aplikáciu. Tá bude bežať ďalej, akurát nebude možné s ňou interagovať.
Spustite si aplikáciu 40krát a máte po RAMke.
V tejto aplikácii ste povinní nastaviť:
frame(defaultCloseOperation: JFrame.EXIT_ON_CLOSE, ...)
Ibaže ak to urobíte a aplikáciu zavriete, umrie celá konzola: vrátane vašich neuložených súborov.
Pozor na to! (Obídenie je neštastné: spúšťať veci z príkazového riadku alebo z Groovy IDE).