Java (27) - seznamy, stromy, tabulky
Prakticky žádná větší aplikace se neobejde bez zobrazování a modifikace
větších datových celků. Obvykle bývají reprezentovány jako různé seznamy,
stromy a tabulky. Framework Swing nám nabízí v tomto ohledu velice užitečný
a snadno použitelný soubor prostředků.
31.8.2006 06:00 |
Lukáš Jelínek
| Články autora
| přečteno 39226×
Modely a jejich smysl
Než přejdeme ke konkrétním objektovým třídám, dovolil bych si nejprve představit
obecný mechanismus práce s daty, který je odděluje od vlastního GUI. Jsou to
tzv. modely. Model není nic jiného než implementace rozhraní, přes které
grafická komponenta přistupuje k datům pro jejich zobrazení a úpravy. Komponenta
tedy nemusí vůbec znát detaily implementace, vystačí si s rozhraním.
K čemu je to dobré? Použití modelů výrazně zlepšuje variabilitu implementace
a umožňuje důsledné oddělení GUI od backendu. Máme-li například tabulku,
mohou být data uložena jak přímo v aplikaci (v kolekcích apod.), tak třeba
i v databázi, klidně i s přímým přístupem (bez mezičlánku v podobě úložiště
v aplikaci). Současně lze manipulace nad daty provádět také jinak než přes GUI,
aniž by se na způsobu uložení dat cokoli měnilo.
Praktická realizace modelů ve Swingu je řešena tak, že máme jednak základní
rozhraní (např. TableModel ), pak abstraktní implementaci (obsahuje často
používanou funkcionalitu pro práci s daty - např. AbstractTableModel ), a
nakonec výchozí implementaci modelu (např. DefaultTableModel - používá se
v případech, kdy nepoužijeme implementaci vlastní). Někdy je hierarchie
složitější (obsahuje např. neproměnné a proměnné modely), komponenty
navíc pracují s více modely, protože kromě uložení dat lze obdobně
využít modely i pro další účely (třeba pro vlastnosti sloupců nebo pro výběry).
Důležitou vlastností modelů je oznamování událostí. Dojde-li ke změně v datech,
model to oznámí všech přihlášeným odběratelům. Již jsem to zmiňoval někdy
dříve, ale opět to připomínám. Využívá se standardní mechanismus práce
s událostmi (tedy vytvoření instance objektu události a její předání
v argumentu metody pro obsluhu dané události).
Seznamy ve Swingu
Můžeme v zásadě rozlišovat dva druhy seznamů: obyčejné (v okénku je nějaký
počet prvků, některé z nich mohou být "vybrané") a rozbalovací, tzv. combo
boxy (normálně je zobrazen jen jediný prvek, při rozbalení se teprve zobrazí
další). Každý z těchto seznamů má svoji třídu a svůj model. Podívejme se na ně
blíže.
Na úvod bych chtěl říci, že ačkoli seznamy obsahují metody pro operace nad daty,
pokud máme přímo k dispozici referenci k modelu, je lepší volat metody modelu.
Tím spíš, že model obecně nemusí podporovat všechny operace, které lze
prostřednictvím seznamu volat.
Obyčejný seznam
Je reprezentován třídou JList a jako model používá rozhraní ListModel .
Rozhraní modelu má pouze 4 metody, pro přidání/odebrání odběratele událostí,
pro zjištění délky seznamu a pro získání určitého prvku seznamu. Text prvku
se získává pomocí metody toString() příslušného prvku, v seznamu mohou bez
problémů být i prvky různých typů.
Co se týká samotného vykreslování, seznam nemá vlastní posuvníky (je to podobné
jako např. u JTextArea ). Proto ho téměř vždy umísťujeme do komponenty
JScrollPane , která posuvníky poskytne. Třída seznamu disponuje obrovským
množstvím metod (což dokazuje, jak velké pole působnosti zde máme) - z
prostorových důvodů se ovšem podíváme jen na několik málo z nich. Zde je
krátký příklad, jak se seznamem pracovat:
DefaultListModel m = new DefaultListModel();
m.addElement("Prvek 1");
m.addElement("Prvek 2");
m.addElement("Prvek 3");
JList list = new JList(m);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
JScrollPane sp = new JScrollPane(list,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
...
Object o = list.getSelectedValue();
V příkladu nejprve vytvoříme instanci výchozího datového modelu a vložíme
do ní prvky. Pak zkonstruujeme seznam (s použitím daného modelu), tomu
nastavíme režim výběru (pouze jednotlivé položky) a vložíme ho do komponenty
s posuvníky. V reálné aplikaci bychom případně pak nastavovali velikost,
pozici apod. Poslední řádek příkladu pak ukazuje, jak zjistit, který prvek
seznamu je vybrán. Tato metoda vrací přímo příslušný prvek (případně null );
pokud potřebujeme index, použije se getSelectedIndex() . Jsou k dispozici i
metody pro vícečetné výběry, ale to v tomto příkladu nemá význam.
Co by se stalo, pokud bychom přidali do modelu nějaký prvek? Výchozí implementace
na to reaguje zavoláním metody fireIntervalAdded() z abstraktní třídy
AbstractListModel , což vyústí v distribuci objektu události. Událost obdrží
též instance třídy JList a proto překreslí seznam. Podobně by se to chovalo
při odebrání jednoho nebo více prvků anebo při jejich změně. Pokud se tato
změna děje uvnitř objektu prvku, není ji ovšem tento model schopen detekovat.
Ještě upřesním, že DefaultListModel je vlastně obal na třídu Vector ,
se vším, co k tomu patří - proto je mnohdy lepší použít vlastní implementaci
modelu, hlavně při častých změnách uvnitř seznamu.
Rozbalovací seznam
Tuto komponentu potřebujeme možná ještě častěji než normální seznam. Je
tvořena třídou JComboBox a modelem ComboBoxModel (potomek rozhraní ListModel
- obsahuje navíc podporu pro výběr prvku, který je zobrazen v nerozbaleném
stavu). Potomkem ComboBoxModel je MutableComboBoxModel , umožňující přidávání
a odebírání prvků.
Combo box se z hlediska použití příliš neliší od běžného seznamu. Protože
ovšem zobrazuje (v nerozbaleném stavu) jen jednu položku, do GUI se začleňuje
podobným způsobem jako textové pole. Rozbalený seznam má v případě potřeby
posuvník, není třeba se o to starat. Výběr může samozřejmě obsahovat pouze
jediný prvek.
Rozlišujeme dva druhy rozbalovacích seznamů - editovatelné a needitovatelné.
U needitovatelných se pracuje pouze s výběrem prvku. Editovatelný seznam
umožňuje upravit vybranou položku, která však implicitně zůstává mimo vlastní
seznam - v modelu se tedy neobjeví a metoda getSelectedIndex() vrací -1.
Potřebujeme-li, aby se upravený prvek objevil v seznamu, musíme se o to
postarat vlastními silami.
Následující příklad ukazuje základy práce s rozbalovacím seznamem:
String sa[] = { "Prvek 1", "Prvek 2", "Prvek 3", "Prvek 4" };
DefaultComboBoxModel m = new DefaultComboBoxModel(sa);
JComboBox cb = new JComboBox(m);
cb.setEditable(true);
cb.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
...
}
}
});
Nejdřív opět vytvoříme výchozí model. Tento příklad ukazuje jiný způsob než
minule - konstruktoru se předává připravené pole prvků. Pak zkonstruujeme
seznam a označíme ho jako editovatelný. Další úsek kódu slouží k reakci na
změnu výběru. Když uživatel vybere prvek ze seznamu (nebo upraví ten, který
je vybraný), provedeme zvolenou akci. Reagovat lze samozřejmě i na zrušení
výběru (odvybrání).
Stromy
Zatímco seznamy byly ve své podstatě velmi jednoduché, stromy jsou o něco
složitější. Stromové datové struktury asi každý zná, stromové zobrazení
v GUI rovněž. Proto by snad nikdo neměl mít problémy s pochopením toho, jak
swingovská implementace stromů funguje.
Začneme modelem. Základem je rozhraní TreeModel , obsahující (kromě známých
metod pro správu odběratelů událostí) metody pro zjištění kořene stromu,
počtu potomků určitého uzlu, přístup k potomkovi přes index, zjištění indexu
potomka a dotaz, zda je uzel listem. Dále je tu také metoda valueForPathChanged() ,
volaná při změně hodnoty uzlu ve stromě. Často používáme výchozí implementaci,
DefaultTreeModel , obsahující ještě řadu dalších metod.
Nelze opomenout třídu TreePath . Představuje cestu, kterou musíme projít
od kořene k nějakému uzlu. Je např. předávána do výše uvedené metody
valueForPathChanged() , ale hodí se i v jiných případech.
Ačkoli rozhraní TreeModel pracuje s obecnými objekty, ve třídě DefaultTreeModel
se používají specializované třídy - implementace rozhraní TreeNode . Má to
své důvody. Pokud bychom totiž neuchovávali informace o stromové hierarchii
v objektech, musel by to dělat model a to není příliš systémové. Rozhraní
TreeNode má potřebné metody, které umožňují zjistit informace o rodičovi
a případných potomcích. Často se používá rozhraní MutableTreeNode , které
přidává navíc ještě manipulační operace.
K dispozici je též výchozí implementace, DefaultMutableTreeNode . V mnoha
případech si s ní vystačíme, tím ovšem vyvstává problém, jak tam napojit
data. Jednoduše - pomocí odkazu na uživatelský objekt. Pokud je odkaz nastaven,
přebírá se zobrazovaný text z tohoto objektu (metoda toString() ).
Třída JTree , představující grafickou komponentu pro kreslení stromu, patří
mezi nejsložitější třídy ve Swingu. Má obrovské množství metod, umožňujících
provádět se stromem všemožné operace. Popisovat je nemá cenu, raději si na
příkladu ukážeme, jak se se stromem pracuje:
void addFiles(File dir, DefaultMutableTreeNode parent) {
File fa[] = dir.listFiles();
for (int i=0; i<fa.length; i++) {
File f = fa[i];
Arrays.sort(fa);
DefaultMutableTreeNode node = new DefaultMutableTreeNode(f.getName());
parent.add(node);
if (f.isDirectory()) {
addFiles(f, node);
}
}
}
File home = new File(System.getProperty("user.home"));
DefaultMutableTreeNode root = new DefaultMutableTreeNode(home.getName());
addFiles(home, root);
DefaultTreeModel m = new DefaultTreeModel(root);
JTree tree = new JTree(m);
JScrollPane sp = new JScrollPane(tree,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
Příklad ukazuje zobrazení adresářového stromu (pod domovským adresářem)
s rekurzivní implementací. Připomínám, že takto by se s adresářovým stromem
pracovat nemělo, může být velice rozsáhlý a jeho načtení by trvalo dlouho.
Vhodnější je načítat obsah adresáře až v okamžiku, kdy se rozbaluje. Zde jsem
ovšem pro jednoduchost zvolil načtení celého stromu naráz.
Podívejme se nejdřív na metodu addFiles() . Ta se volá rekurzivně (nebyl by
samozřejmě problém to převést na iterační řešení) a zajišťuje zpracování
obsahu adresáře, tedy vytvoření podstromu tohoto adresáře. Asi není co příliš
vysvětlovat, prostě se získá seznam souborů, ten se abecedně seřadí a
jednotlivé soubory se přidají do stromu jako potomci aktuálního uzlu. Pokud je
daný soubor adresářem, metoda se na něj opět zavolá.
Při samotném použití pak zjistíme domovský adresář (získáme ho z vlastností
systému), vytvoříme pro něj kořenový uzel a zavoláme výše popsanou metodu.
Až je logický strom vytvořen, zkonstruujeme pro něj model a ten předáme
konstruktoru grafické komponenty stromu. Zbývá už jen vložit komponentu
do oblasti s posuvníky.
Když už jsem zmínil to postupné načítání při rozbalování, naznačím ještě, jak
by se to dělalo. Existuje rozhraní TreeWillExpandListener , které slouží ke
zpracování událostí generovaných stromem před tím, než se rozbalí nebo sbalí
větev pod uzlem. Vytvoříme si tedy implementaci rozhraní a zaregistrujeme ji
u stromu:
tree.addTreeWillExpandListener(new TreeWillExpandListener() {
public void treeWillExpand(TreeExpansionEvent e)
throws ExpandVetoException {
DefaultMutableTreeNode node =
(DefaultMutableTreeNode) e.getPath().getLastPathComponent();
...
}
public void treeWillCollapse(TreeExpansionEvent e)
throws ExpandVetoException {}
});
Příklad pracuje s anonymní třídou. Implementovat musíme samozřejmě obě metody
rozhraní, i když používáme jen jednu z nich. Objekt události obsahuje cestu
k uzlu, který bude rozbalen. Lze snadno získat přímo tento uzel, jak je
v příkladu ukázáno. Pokud bychom ale takto implementovali načítání adresářového
stromu, takto jednoduché by to nebylo. Buď bychom museli jako uživatelský
objekt stromu použít něco jiného než obyčejný řetězec s název souboru
(objekt File použíte nejde - ve stromě by se zobrazovaly celé cesty), anebo
cestu k načítanému adresáři sestavit z komponent cesty k uzlu (TreePath ).
Tabulky
Posledním objektem v tomto dílu seriálu bude tabulka, představovaná třídou
JTable . Nemusím snad připomínat, že základem budou opět modely, jeden pro
samotnou tabulku, druhý pro sloupce.
Model tabulky má metody pro zjištění počtu řádků a sloupců, získání a uložení
hodnoty buňky, zjištění názvu a třídy sloupce (viz dále), a zjištění
editovatelnosti buňky. Třída sloupce má zásadní význam - ovlivňuje totiž
zobrazování a případné úpravy buněk v daném sloupci (různé např. pro textové
řetězce a pravdivostní hodnoty). Sloupcový model umožňuje velice detailní
ovlivnění toho, jak se bude se sloupci tabulky zobrazovat. Bohužel na
tu dostatečný popis není dost prostoru, proto ho musím vynechat.
Pro případy, kdy si vytváříme vlastní model pro tabulková data, je nejlepší
použít abstraktní třídu AbstractTableModel . Povinně se implementují pouze
tři metody (getRowCount() , getColumnCount() a getValueAt() ) - pokud si
vystačíme s read-only režimem, nepotřebujeme pro základní funkcionalitu nic dalšího.
Pro editovatelné buňky musíme předefinovat metody setValueAt() a isCellEditable() .
Obvykle je ale vhodné předefinovat i další metody (názvy sloupců, jejich třídy
atd.). Uložení dat je plně v naší režii, při jejich změně se musíme postarat
o zavolání příslušných metod fireXXX() , aby byli informováni odběratelé
událostí. K implementaci vlastního tabulkového modelu se ještě vrátíme později,
až se dostaneme k problematice databází.
V jednodušších případech si lze vystačit s výchozí implementací modelu, třídou
DefaultTableModel . Ta používá pro tabulkové řádky kolekce typu Vector , a
ukládá je opět do instance Vector . Totéž platí i pro názvy sloupců. Model
automaticky generuje události při změnách v datech, změny uvnitř datových
objektů však samozřejmě není schopen detekovat. Všechny buňky jsou v tomto
modelu editovatelné.
Pro metody třídy JTable platí totéž, co jsem uvedl u předchozích grafických
komponent - je jich mnoho a popisovat je nemá smysl. Proto bude lepší si je
opět ukázat na příkladu:
DefaultTableModel m = new DefaultTableModel();
m.addColumn("Název");
m.addColumn("Hodnota");
Properties p = System.getProperties();
Iterator<Object> it = p.keySet().iterator();
while (it.hasNext()) {
String key = (String) it.next();
String row[] = { key, p.getProperty(key) };
m.addRow(row);
}
JTable table = new JTable(m);
JScrollPane sp = new JScrollPane(table,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
Příklad ukazuje tabulku vlastností systému. V prvním sloupci je název
vlastnosti, ve druhém její hodnota. S vlastnostmi systému jsme se setkali jak
v tomto článku, tak i v některých dřívějších - jsou to textové hodnoty
popisující prostředí, v němž běží program (např. domovský a pracovní adresář,
oddělovač adresářových cest, kódování znaků a mnoho dalších parametrů).
Hodnoty se zobrazují neseřazené.
V příkladu se nejprve vytvoří prázdný model a přidají se do něj dva sloupce.
Pak se postupně přidávají jednotlivé hodnoty - získávají se pomocí iterátoru.
Další kroky jsou pak triviální (a stejné jako u přechozích příkladů),
vytvoří se instance JTable a ta se vloží do grafického kontejneru s posuvníky.
Kdo si příklad vyzkouší ("omáčka" je pochopitelně vynechána a každý si ji
doplní sám - např. na základě příkladů z přechozích kapitol), možná zjistí, že
může obsah kterékoli buňky bez problémů změnit (dvojklikem na buňce se
spustí editace). To je přesně v souladu s tím, jak jsem popsal chování třídy
DefaultTableModel . Změněné hodnoty se pochopitelně nijak nepromítnou do
nastavení příslušných systémových vlastností.
Vlastní zobrazení - proč ne?
Příští kapitola naváže na tuto a bude věnována vlastním zobrazovačům
(rendererům) a editorům. Při zobrazování a úpravách hodnot v tabulkách
a dalších grafických objektech totiž nejsme zdaleka omezeni jen tím, co nám
tyto komponenty nabízejí. Můžeme si vytvořit vlastní třídy, které se budou
chovat tak, jak právě potřebujeme. Potřebujeme třeba zobrazovat záporná čísla
červeně nebo při editaci kontrolovat formát vkládaných dat. Obojí, a ještě
mnoho dalšího, lze bez problémů zařídit.
Verze pro tisk
|
Příspívat do diskuze mohou pouze registrovaní uživatelé.
|
|
![>](img/sipka2.png)
Vyhledávání software
![>](img/sipka2.png)
Vyhledávání článků
28.11.2018 23:56 /František Kučera Prosincový sraz spolku OpenAlt se koná ve středu 5.12.2018 od 16:00 na adrese Zikova 1903/4, Praha 6. Tentokrát navštívíme organizaci CESNET. Na programu jsou dvě přednášky: Distribuované úložiště Ceph (Michal Strnad) a Plně šifrovaný disk na moderním systému (Ondřej Caletka). Následně se přesuneme do některé z nedalekých restaurací, kde budeme pokračovat v diskusi.
Komentářů: 1
12.11.2018 21:28 /Redakce Linuxsoft.cz 22. listopadu 2018 se koná v Praze na Karlově náměstí již pátý ročník konference s tématem Datová centra pro business, která nabídne odpovědi na aktuální a často řešené otázky: Jaké jsou aktuální trendy v oblasti datových center a jak je optimálně využít pro vlastní prospěch? Jak si zajistit odpovídající služby datových center? Podle jakých kritérií vybírat dodavatele služeb? Jak volit vhodné součásti infrastruktury při budování či rozšiřování vlastního datového centra? Jak efektivně datové centrum spravovat? Jak co nejlépe eliminovat možná rizika? apod. Příznivci LinuxSoftu mohou při registraci uplatnit kód LIN350, který jim přinese zvýhodněné vstupné s 50% slevou.
Přidat komentář
6.11.2018 2:04 /František Kučera Říjnový pražský sraz spolku OpenAlt se koná v listopadu – již tento čtvrtek – 8. 11. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma umění a technologie, IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář
4.10.2018 21:30 /Ondřej Čečák LinuxDays 2018 již tento víkend, registrace je otevřená.
Přidat komentář
18.9.2018 23:30 /František Kučera Zářijový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 20. 9. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář
9.9.2018 14:15 /Redakce Linuxsoft.cz 20.9.2018 proběhne v pražském Kongresovém centru Vavruška konference Mobilní řešení pro business.
Návštěvníci si vyslechnou mimo jiné přednášky na témata: Nejdůležitější aktuální trendy v oblasti mobilních technologií, správa a zabezpečení mobilních zařízení ve firmách, jak mobilně přistupovat k informačnímu systému firmy, kdy se vyplatí používat odolná mobilní zařízení nebo jak zabezpečit mobilní komunikaci.
Přidat komentář
12.8.2018 16:58 /František Kučera Srpnový pražský sraz spolku OpenAlt se koná ve čtvrtek – 16. 8. 2018 od 19:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát jsou tématem srazu databáze prezentaci svého projektu si pro nás připravil Standa Dzik. Dále bude prostor, abychom probrali nápady na využití IoT a sítě The Things Network, případně další témata.
Přidat komentář
16.7.2018 1:05 /František Kučera Červencový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 19. 7. 2018 od 18:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát bude přednáška na téma: automatizační nástroj Ansible, kterou si připravil Martin Vicián.
Přidat komentář
Více ...
Přidat zprávičku
![>](img/sipka2.png) Poslední diskuze
31.7.2023 14:13 /
Linda Graham iPhone Services
30.11.2022 9:32 /
Kyle McDermott Hosting download unavailable
13.12.2018 10:57 /
Jan Mareš Re: zavináč
2.12.2018 23:56 /
František Kučera Sraz
5.10.2018 17:12 /
Jakub Kuljovsky Re: Jaký kurz a software by jste doporučili pro začínajcího kodéra?
Více ...
|