Ako renderovať prvky JList-u po svojom?

JList v Jave nie je len hlúpa trieda pre zoznamy, ktorá zobrazuje reťazce pod sebou a tam končí. Je značne flexibilná a nie je veľký problém dosiahnuť všakovaké výzory objektov, ktoré bude používateľ obdivovať.

Urobme si napr. zoznam kontaktov, a.k.a. paródiu na instant messenger (po slovenský okamžitý správovník).

Tu, hľa, výsledný stav:

Čo budeme potrebovať?

  • objekt pre kontakty
  • JList so zoznamom a jeho modelom
  • okno, ktoré to zobrazí
  • vec, ktorá zobrazuje kontakty krajšie než bežný prací prášok, pardón, krajšie než obvykle.

Objekt pre kontakty

Kontakt. Čo dodať? Kontakt bude niesť nick a indikáciu toho, či je online alebo offline.

Trieda je nudné POJO: samé gettre, settre a konštruktory.

package sk.upjs.ics.paz1c.cellrenderer;

public class Contact {
    private String nick;

    private boolean isOnline;

    public Contact(String nick) {
        this.nick = nick;
    }

    public void setOnline(boolean isOnline) {
        this.isOnline = isOnline;
    }

    public String getNick() {
        return nick;
    }

    public boolean isOnline() {
        return isOnline;
    }
}

Okienečko so zoznamom, diel 1.

Okno so zoznamom bude opäť nuda. Vytvoríme jeden JList a napcháme ho do JFrame:

package sk.upjs.ics.paz1c.cellrenderer;

import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;

public class ContactListFrame extends JFrame {
    public ContactListFrame() {
        setTitle("Contacts");

        JList contactList = new JList();
        add(contactList);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                ContactListFrame contactListFrame = new ContactListFrame();
                contactListFrame.setVisible(true);
                contactListFrame.pack();
            }
        });
    }
}

Po spustení dostaneme skvelú vec:

Hm, skúsme to natiahnuť:

O nič lepšie?

Vyzerá to divne, ale naozaj problém spočíva v tom, že zoznam nemá žiadne dáta. Dodajme ich teda!

Model zoznamu

Ako je známe, zoznam JList ťahá dáta z modelu. V tomto prípade nemusíme šaškovať s vlastnou implementáciou, ale rovno využijeme konštruktor JListu, do ktorého narveme pole objektov. Sám JList si potajme vytvorí inštanciu modelu (zrejme DefaultListModel) a bude ju používať.

Navyrábajme teda kontakty a napchajme ich do zoznamu:

Contact john = new Contact("john");
john.setOnline(true);

Contact lea = new Contact("lea");

Contact sokumi = new Contact("sokumi");
sokumi.setOnline(true);

Contact[] contacts = { john, lea, sokumi };

JList contactList = new JList(contacts);

Spusťme to a…

…hrôza! Podivnosti, náhodné čísla. Znalci vedia, že príčinou je chýbajúca metóda toString()v triede Contact. Zoznam JList štandardne využije na zobrazenie objektov práve túto metódu.

public class Contact {
    /*...*/
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(nick).append(" [");
        sb.append(isOnline ? "online" : "offline");
        sb.append("]");

        return sb.toString();
    }
}

Po druhom spustení je to lepšie.

Vec, ktorá zobrazuje kontakty krajšie než obvykle

Vec, ktorá prevezme objekt z modelu a namaľuje ho do JListu je vyčlenená pomocou návrhového vzoru strategy. Ak vezmete objekt typu ListCellRenderer a nastavíte ho na JListe cez setCellRenderer(), môžete si dľa nálady meniť spôsob zobrazovania položiek. Presnejšie povedané, tento objekt mapuje objekty z modelu na swingovské komponenty.

Implicitný ListCellRenderer je DefaultListCellRenderer, ktorý vezme objekt z modelu, zavolá na ňom toString() a výsledok zobrazí v utajenom JLabeli.

Chcete zmeniť text objektu? (Bez zmeny toString()u?) Nie je nič jednoduchšie, stačí oddediť od DefaultListCellRenderera, zobrať objekt z modelu, vytiahnuť príslušný reťazec a poslať ho do rodiča, nech ho zobrazí po svojom:

package sk.upjs.ics.paz1c.cellrenderer;

import java.awt.Component;

import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JList;

public class ContactListCellRenderer extends DefaultListCellRenderer {

    @Override
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
        Contact contact = (Contact) value;
        String displayedText = contact.getNick(); 
        return super.getListCellRendererComponent(list, displayedText, index, isSelected, cellHasFocus);
    }
}

Nezabudneme nastaviť tento cell renderer na objekte JList!

JList contactList = new JList(contacts);
contactList.setCellRenderer(new ContactListCellRenderer());

Zrazu to bude vyzerať nasledovne:

Vec, ktorá zobrazuje kontakty ešte krajšie než obvykle

Vytvorme si dva obrážteky: online.png (zelený kruh) a offline.png (červený kruh) a vložme ich do adresára so zdrojákmi.

Ako vieme zobraziť obrážteky vedľa textu?

Vyššie bol spomínané, že DefaultCellRenderer prevádza objekty na JLabely. Dokumentácia k tejto triede je pomerne záludná, ale dajú sa vytušiť dve veci:

  • DefaultCellRenderer dedí od JLabel (och, divná to dedičnosť)
  • metóda getListCellRendererComponent() v tejto triede vracia ako výsledok samú seba.

Áno, trieda je JLabel a zároveň ListCellRenderer, ktorý na sebe nastaví rozličné vlastnosti podľa modelu (text, obrázok) a potom vráti samú seba ako prvok, ktorý sa má namaľovať do JListu.

Toto nie je popísané explicitne, ale dá sa to vytušiť zo zdrojákov… a všetci sa na to spoliehajú.

Tam, kde existuje JLabel, možno nastaviť nielen text (setText()), ale aj obrážtek (setIcon()).

Na reprezentáciu obrázkov slúži v Swingu trieda ImageIcon, ktorá načíta obrázok z ľubovoľnej cesty. Ak využijeme fintu s classpathom, vyriešime naháňanie obrázkov po projekte (tie budú bok po boku s .class súbormi.)

Vytvorme teda dva ImageIcony a na základe toho, či je kontakt online alebo offline a vyrobíme kompletný dokonalý renderer:

public class ContactListCellRenderer extends DefaultListCellRenderer {
    private ImageIcon onlineIcon;
    private ImageIcon offlineIcon;

    public ContactListCellRenderer() {
        onlineIcon = new ImageIcon(ContactListCellRenderer.class.getResource("online.png"));
        offlineIcon = new ImageIcon(ContactListCellRenderer.class.getResource("offline.png"));
    }

    @Override
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
        Contact contact = (Contact) value;
        String contactText = contact.getNick(); 
        JLabel label = (JLabel) super.getListCellRendererComponent(list, contactText, index, isSelected, cellHasFocus);

        if(contact.isOnline()) {
            label.setIcon(onlineIcon);
        } else {
            label.setIcon(offlineIcon);
        }

        return label;
    }

}

Nezabudneme overiť, že je naozaj nastavený a môžeme obdivovať farebný zoznam kontaktov, ako sme videli na začiatku.

Pridaj komentár

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