Swing umožňuje customizovať zobrazené položky v JListe 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 SwingBuilderi?
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
JListom
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 ListCellRenderera. 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 ListCellRendereri.
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 JLabelu: 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 ClosureRenderera
Záhadou je premenná value. V SwingBuilderi 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 SwingBuilderi:
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)
}
}
}
}
}
}
}