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 JList
u, 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 JList
e 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 JLabel
i.
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 JLabel
y. Dokumentácia k tejto triede je pomerne záludná, ale dajú sa vytušiť dve veci:
DefaultCellRenderer
dedí odJLabel
(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 JList
u.
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 ImageIcon
y 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.