Swing umožňuje customizovať zobrazené položky v JList
e pomocou ListCellRenderera
: inými slovami cellrenderer mapuje objekty z modelu na komponenty, ktoré sa zobrazia v JListe
.
Dostatočne je to rozpitvané článku Ako renderovať prvky JListu po svojom, lež ten sa týka Javy.
Ako na to v Groovy a SwingBuilder
i?
Využijeme malý príklad s kontaktami a ich online statusmi.
Budeme potrebovať:
- vlastnú implementáciu ikony
Icon
, kde si sami namaľujeme kruh podľa statusu (onlajn budú zelení, oflajn siví) - dáta s kontaktmi
- vlastný cellrenderer: ukážeme si dve možnosti
- prepojenie cellrenderera s
JList
om
Vlastná implementácia ikony
Ikona Icon
je interfejs, kde prekryjeme tri metódy: určenie výšky, šírky a samotné maľovanie.
Programujme v Groovy:
class ColorCircleIcon implements Icon {
def color = Color.GREEN
int getIconHeight() {
return 32;
}
int getIconWidth() {
return getIconHeight();
}
void paintIcon(Component c, Graphics g, int x, int y) {
// zapamatame si povodnu farbu
Color originalColor = g.color
// nastavime pozadovanu farbu vyplne
g.color = this.color
// malujeme
g.fillOval(x, y, getIconWidth(), getIconHeight())
// obnovime povodnu farbu = upraceme po sebe
g.color = originalColor
}
}
Dáta s kontaktmi
Keďže mapa vie veľmi jednoducho simulovať objekt bez metód, nebudeme sa s tým párať:
def contacts = [
[ name: "John", online: true],
[ name: "Paul" ],
[ name: "Susan", online: true],
]
Cellrenderer
Pomocná metóda list()
má atribút cellRenderer
, ktorému môžeme priradiť inštanciu ListCellRenderer
a. Tú vybudujeme pomocou metódy listCellRenderer()
, v ktorej špecifikujeme closure s renderujúcim kódum v atribúte onRender
. Áno, syntax je podivná.
SwingBuilder.edtBuilder {
frame(show: true, pack: true) {
scrollPane() {
list = list(items: contacts,
cellRenderer : listCellRenderer {
onRender {
def renderedComponent = children[0]
renderedComponent.text = value.name
if(value.online) {
renderedComponent.icon = new ColorCircleIcon(color: Color.GREEN)
} else {
renderedComponent.icon = new ColorCircleIcon(color: Color.GRAY)
}
return rendererComponent
}
}
)
}
}
}
Najväčší špinavý trik spočíva v záludnom children[0]
(je ukradnutý z issue GROOVY-4474). Deklarácia listCellRenderer()
v sebe môže niesť budovacích potomkov, ktorí sa použijú ako komponent v návratovej hodnote metódy getListCellRendererComponent()
v klasickom javáckom ListCellRenderer
i.
V našom prípade nemáme deklarovaných žiadnych potomkov: v skutočnosti však SwingBuilder
dodá implicitný DefaultListCellRenderer
, ktorý sa objaví na nultej pozícii v zozname potomkov. Poznačíme si ho do premennej rendererComponent
.
Zhodou okolností je DefaultListCellRenderer
aj podtriedou JLabel
u: to je skvelé, lebo mu môžeme nastaviť text
(analógia JLabel#setText()
) i ikonu (analógia JLabel#setIcon()
). Ikonu si vyrobíme, nastavíme ju, a celý vyrenderovaný komponent vrátime na vykreslenie do zoznamu JList
.
Odkiaľ je premenná value
? Z ClosureRenderer
a
Záhadou je premenná value
. V SwingBuilder
i sa používa groovystická implementácia groovy.swing.impl.ClosureRenderer
, ktorá sa vie tváriť ako renderer pre zoznamy, tabuľky, i stromy.
Vo vnútri “metódy” onRender
sa tak vieme odkazovať na jej metódy: máme tak k dispozícii getValue()
(resp. value
), ale aj row
s poradovým číslom aktuálneho riadka v modeli, alebo napr. getList()
, ktorý vráti vlastníka tohto renderera.
Ak používate tento renderer napr. pre tabuľky, pokonzultujte dokumentáciu k tejto triede: nájdete tam aj ďalšie užitočné premenné.
Alternatívna syntax
Alternatívna syntax umožňuje použiť cellrenderer ako vnorený komponent zoznamu list()
v SwingBuilder
i:
list = list(items: contacts) {
cellRenderer {
onRender {
def renderedComponent = children[0]
renderedComponent.text = value.name
if(value.online) {
renderedComponent.icon = new ColorCircleIcon(color: Color.GREEN)
} else {
renderedComponent.icon = new ColorCircleIcon(color: Color.GRAY)
}
}
}
}
Celý kód
import groovy.swing.SwingBuilder
import javax.swing.*
import javax.swing.event.*
import java.awt.BorderLayout
import java.awt.Color
import java.awt.Component
import java.awt.Graphics
class ColorCircleIcon implements Icon {
def color = Color.GREEN
int getIconHeight() {
return 32;
}
int getIconWidth() {
return getIconHeight();
}
void paintIcon(Component c, Graphics g, int x, int y) {
Color originalColor = g.color
g.color = this.color
g.fillOval(x, y, getIconWidth(), getIconHeight())
g.color = originalColor
}
}
def contacts = [
[ name: "John", online: true],
[ name: "Paul" ],
[ name: "Susan", online: true],
]
SwingBuilder.edtBuilder {
frame(show: true, pack: true) {
scrollPane() {
list = list(items: contacts) {
cellRenderer {
onRender {
def renderedComponent = children[0]
renderedComponent.text = value.name
if(value.online) {
renderedComponent.icon = new ColorCircleIcon(color: Color.GREEN)
} else {
renderedComponent.icon = new ColorCircleIcon(color: Color.GRAY)
}
}
}
}
}
}
}