Groovy SwingBuilder a vlastné zobrazovanie položiek v zoznamoch

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)
                    }
                }
            }
        }
    }
  }
}

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *