Image
27.6.2016 0 Comments

Programujeme v Jave /7.časť

Späť na úvod >> Späť na programovanie >> Späť na seriál

Komponenty JTable a JTree

Aj keď sa nám rok prehupol do druhej polovice a vonku zúria leto a prázdniny, možno ste si našli chvíľku času na to, aby ste zasadli za svoj počítač a venovali sa ďalšiemu pokračovaniu nášho seriálu o praktickom programovaní v Jave. Verím, že vás zaujme, pretože sa budeme venovať veľmi praktickej téme, a to komponentom JTable (podporné triedy sú umiestnené v balíku javax.swing.table) a JTree (javax.swing.tree), ktoré sú súčasťou knižnice Swing. Ako podklad na prácu s nimi nám poslúži predchádzajúca časť, v ktorej sme sa oboznámili s dátovými modelmi a architektúrou MVC. Pokiaľ ste teda z nejakého dôvodu ešte predošlú časť nepreštudovali, odporúčam vám tak spraviť ešte prv, než sa začnete venovať tej dnešnej.

JTable

Komponent JTable predstavuje veľmi komplexný pohľad na tabuľku. Aby ste správne pochopili termín pohľad, spomeňte si na predchádzajúcu časť a architektúru MVC (model/view/controller) – komponent JTable je možné chápať z aspektu tejto architektúry ako písmeno V, alebo ak chcete, View, teda po slovensky pohľad. Už v predošlom pokračovaní ste mali možnosť oboznámiť sa s komponentmi, ktoré predstavujú iba určitý pohľad na dáta, pričom tieto dáta sú v skutočnosti poskytované inými, nevizuálnymi komponentmi. Preto na prácu s nimi je potrebné používať viacero podporných tried. My začneme rozprávanie o triede JTable jednoduchšími príkladmi a postupne sa prepracujeme k zložitejším črtám jej architektúry MVC.

Komponent JTable je možné skonštruovať viacerými spôsobmi. Najjednoduchšie je použiť konštruktor preberajúci ako celočíselné parametre počet riadkov a počet stĺpcov tabuľky:

// skonštruujeme tabuľku 5x4
JTable table = new JTable(5, 4);

Pokiaľ by ste chceli hneď v konštruktore inicializovať bunky tabuľky nejakými východiskovými hodnotami, môžete použiť konštruktor prijímajúci dvojrozmerné pole objektov typu Object a pole objektov opisujúcich jednotlivé stĺpce:

// vytvoríme maticu dát
String[][] data = {{“1”, “2”, “3”},
                   {“4”, “5”, “6”},
                   {“7”, “8”, “9”}};
// vytvoríme pole s opisom stĺpcov
String[] names = {“Prvý”, “Druhý”, “Tretí”};
// skonštruujeme tabuľku
JTable table = new JTable(data, names);

S komponentom JTable budete najčastejšie používať aj komponent JScrollPane, pretože v prípade, že tabuľku nezapuzdríte do JScrollPane a umiestnite ju do kontajnera priamo, nebudú zobrazované záhlavia stĺpcov:

JScrollPane jsp = new JScrollPane(table);
getContentPane()add(jsp);

Dátový model komponentu JTable

Trieda, ktorú chcete použiť ako dátový model pre JTable, musí implementovať rozhranie TableModel definovaný v balíku javax.swing.table. Toto rozhranie obsahuje nasledujúce metódy:

addTableModelListener() – metóda slúžiaca na pridanie poslucháča typu TableModelListener, ktorého metóda tableChanged() je volaná pri každej zmene dát. Väčšinou je poslucháčom samotná tabuľka, aby mohla prekresliť svoj obsah pri zmene dát.

removeTableModelListener() – odregistruje poslucháča typu TableModelListener zo zoznamu poslucháčov.

getColumnClass() – metóda vracia objekt typu Class, ktorý opisuje typ dát uložených v stĺpci s indexom daným ako parametrom.

getColumnCount() – metóda vracajúca počet stĺpcov tabuľky.

getColumnName() – metóda vráti názov stĺpca s indexom daným ako parameter.

getRowCount() – metóda vracajúca počet riadkov v tabuľke (vynímajúc záhlavie).

getValueAt() – metóda vráti hodnotu v riadku a stĺpci, zadanými ako parametre, hodnota je vrátená ako objekt typu Object.

setValueAt() – metóda slúžiaca na nastavenie hodnoty v riadku a stĺpci, zadanými ako parametre, hodnota je odovzdávaná ako prvý parameter, ako objekt typu Object.

isCellEditable() – metóda vráti logickú hodnotu true alebo false na základe toho, či je alebo nie je možné editovať bunku v riadku a stĺpci danými ako parametre.

Implementácia všetkých metód môže byť niekedy zdĺhavá a hlavne zbytočná záležitosť, keďže existujú už preddefinované triedy ako AbstractTableModel a DefaultTableModel, ktoré nám implementáciu vlastného dátového modelu značne uľahčujú.

Trieda AbstractTableModel je abstraktná a poskytuje základnú funkčnosť dátového modelu tabuľky implementáciou väčšiny metód rozhrania TableModel. Jedinými tromi metódami, ktorých implementáciu ponecháva na aplikačnom programátorovi, sú metódy getRowCount(), getColumnCount() a getValueAt(). Pri vytváraní konkrétnej triedy rozširujúcej triedu AbstractTableModel sa musíte teda postarať iba o správne určenie počtu stĺpcov a riadkov a správne určenie hodnoty uloženej na danej pozícii. Pozrime sa, ako môže taká ukážková implementácia triedy odvodenej od AbstractTableModel vyzerať:

  class MyTableModel extends AbstractTableModel {
 
    private Object[][] values;
   
    public MyTableModel(Object[][] values) {
      this.values = values;
    }
 
    public int getRowCount() {
      return values.length;
    }
 
    public int getColumnCount() {
      return values[0].length;
    }
 
    public Object getValueAt(int row, int column) {
      return values[row][column];
    }
 
  }

Ako vidíte, pri inštanciácii objektu nášho dátového modelu treba odovzdať konštruktoru odkaz na maticu hodnôt typu Object, ktoré budú zobrazované v jednotlivých bunkách tabuľky. Počet riadkov je určený počtom polí, ktoré premenná values obsahuje (premenná values je matica, teda pole polí). Počet stĺpcov je determinovaný počtom prvkov prvého poľa matice. Keď si zobrazovací komponent (v našom prípade komponent JTable) vyžiada hodnotu konkrétnej bunky v tabuľke, je mu jednoducho odovzdaný odkaz na objekt s príslušnými hodnotami riadkového a stĺpcového indexu. Náš novovytvorený dátový model môže byť použitý napr. takto:

    Object[][] values = {{"1", "2", "3"}, {"4", "5", "6"}, {"7", "8", "9"}};
    getContentPane().add(new JTable(new MyTableModel(values)));

Takto vytvorená tabuľka bude obsahovať tri riadky a tri stĺpce s postupne vypísanými hodnotami od 1 po 9.

DefaultTableModel

Trieda DefaultTableModel sa používa vždy, keď pri konštrukcii komponentu JTable nepoužijete konštruktor preberajúci ako jeden z parametrov inštanciu dátového modelu, reprezentovanú ako odkaz na triedu implementujúcu rozhranie TableModel. Ide teda o analógiu s komponentmi preberanými v predošlej časti, pretože v prípade použitia konštruktora preberajúceho dáta v inej podobe než zapuzdrené v dátovom modeli alebo v prípade použitia bezparametrického konštruktora bude použitý defaultný dátový model DefaultTableModel, do ktorého budú získané dáta importované. Trieda DefaultTableModel je konkrétnou implementáciou rozhrania TableModel a je odvodená od AbstractTableModel. K tomu pridáva niekoľko metód, ktoré značne uľahčujú manipuláciu so zapuzdrenými dátami. Pomocou metód addRow(), insertRow() a addColumn() môžete pridávať riadky a stĺpce hodnôt, pomocou metód removeRow() a moveRow() riadky mazať alebo premiestňovať. Môžete takisto nastaviť konkrétnu hodnotu jedinej bunky prostredníctvom metódy setValueAt(). Na presnú špecifikáciu a zoznam parametrov týchto metód stačí nahliadnuť do dokumentácie k API.

Ako vidíte, vďaka triede DefaultTableModel často nebudete nútení implementovať vlastnú triedu s rozhraním TableModel. V nasledujúcom príklade nadviažeme na predchádzajúcu ukážku, tentoraz však vo verzii s použitím DefaultTableModel. Pridáme aj zapuzdrenie zobrazovacieho komponentu JTable do komponentu JScrollPane. To nám umožní vidieť aj záhlavia stĺpcov (ak ste si predchádzajúci príklad testovali, určite ste si všimli, že záhlavia stĺpcov nie sú viditeľné). Pre úsporu miesta uvádzame len metódu init():

  public void init() {
    DefaultTableModel tableModel = new DefaultTableModel(3, 3);
    int value = 0;
    for (int i = 0; i < 3; i++)
      for (int j = 0; j < 3; j++)
        tableModel.setValueAt(String.valueOf(++value), i, j);
    JTable table = new JTable(tableModel);
    getContentPane().add(new JScrollPane(table));
  }

Isteže je možné naplniť dátový model viacerými spôsobmi, tu som vám predstavil ten najjednoduchší – nastavenie hodnoty každej bunky osobitne v cykle. Podstatnejší tu však je fakt, že sme nemuseli implementovať vlastnú triedu dátového modelu.

JTree

Ďalším zaujímavým komponentom, ktorý priniesla knižnica Swing, je komponent JTree. Ako už jeho názov napovedá, ide o komponent, ktorý vykreslí údaje štruktúrované do stromu, aký máte možnosť vidieť napr. aj v aplikácii Prieskumník, ktorý v strome zobrazuje adresárovú štruktúru.

Zložitejšie použitie komponentu JTree vyžaduje znalosť viacerých tried definovaných v balíčku javax.swing.tree, ale základy, ako vytvorenie jednoduchého stromu s niekoľkými vetvami, si môžeme demonštrovať na príklade. Využijeme v ňom konštruktor JTree preberajúci ako parameter pole objektov typu Object, ktoré reprezentujú hodnoty v strome.

Každý strom má práve jeden uzol na najvyššej úrovni, nazývaný aj koreňový uzol alebo skrátene koreň. Zobrazovanie tohto koreňového uzla je implicitne vypnuté. Zmenu tohto nastavenia je možné vykonať volaním metódy setRootVisible().

Poďme už na sľúbený príklad:

 
import javax.swing.*;
import javax.swing.tree.*;
import java.awt.*;
 
public class JTreeExample extends JApplet {
 
  public void init() {
    Object[] data = {"Paul", "John", "George", "Ringo"};
    JTree tree = new JTree(data);
    tree.setRootVisible(true);
    getContentPane().add(tree);
  }
 
}

Iste vás už napadla otázka, čo robiť v prípade, že chceme vložiť do stromu uzly, ktoré obsahujú ďalšie poduzly. Riešením je definovať danú položku poľa, ktorým komponent JTree inicializujeme, ako pole alebo objekt typu Vector. Lepšie je použitie triedy Vector, pretože takýto prístup nám umožní prekryť metódu toString(), ktorej návratová hodnota je použitá ako názov tohto uzla. Položky vložené do tejto triedy potom reprezentujú jej dcérske uzly. Na objasnenie uvádzam nasledujúci príklad (metóda init()):

  public void init() {
    Object[] data = {"", "Help!", "Revolver", "Abbey road", "Let it be"};
    Vector ff = new Vector() {
      public String toString() {
        return "Fab Four";
      }
    };
    ff.add("Paul");
    ff.add("John");
    ff.add("George");
    ff.add("Ringo");
    data[0] = ff;
    JTree tree = new JTree(data);
    tree.setRootVisible(true);
    getContentPane().add(tree);
  }

Myslím, že teraz by vám už mal byť základný spôsob použitia triedy JTree jasný. Viete vytvoriť tento vizuálny komponent, ako aj naplniť ho dátami do ľubovoľnej úrovne (aj keď možno trošku ťažkopádne).

Dátový model komponentu JTree

Je načase, aby sme si povedali, akým spôsobom sú uložené dáta, ktoré sú zobrazené komponentom JTree. Podobne ako ostatné komponenty, s ktorými ste sa oboznámili v tomto seriáli, používa tento komponent svoj dátový model, ktorý je reprezentovaný objektom triedy implementujúcej rozhranie TreeModel, definované v triede javax.swing.tree. Toto rozhranie deklaruje metódy na pridávanie a odoberanie poslucháčov udalostí súvisiacich so zmenami v dátovom modeli, ktorými sú addTreeModelListener() a removeTreeModelListener().

Metóda getChild() vráti objekt reprezentujúci dcérsky uzol objektu, ktorý jej odovzdáte ako parameter. Takisto musíte špecifikovať index dcérskeho uzla, ktorého odkaz chcete získať, pričom tieto uzly sú indexované od 0 v poradí, v akom boli k uzlu pridané. Metóda getChildCount() vráti počet dcérskych uzlov uzla zadaného ako parameter. Na zistenie indexu umiestnenia v rodičovskom uzle dcérskeho uzla využijete metódu getIndexOfChild().

Rozhranie ďalej deklaruje metódu getRoot(), ktorá vráti objekt reprezentujúci koreňový uzol stromu. Metóda isLeaf() signalizuje, či uzol zadaný ako parameter je listom stromu, alebo nie. Listom stromu je taký uzol, ktorý nemá alebo nemôže mať nijaké dcérske uzly.

Poslednou metódou deklarovanou rozhraním TreeModel je valueForPathChanged(), ktorá je volaná pri každej zmene hodnoty uzla.

Určite vás poteší, že opäť existuje konkrétna implementácia tohto rozhrania s názvom DefaultTreeModel, ktorej inštancie sú pri vytváraní stromov používané najčastejšie. Dá sa teda predpokladať, že si pri tvorbe aplikácií vystačíte s touto triedou aj vy.

Uzly stromu

Každý uzol stromu je reprezentovaný ako objekt triedy implementujúcej rozhranie TreeNode. Toto rozhranie deklaruje metódy, na ktoré sú mapované volania metód dátového modelu TreeModel. Jednotlivé metódy tu nebudeme rozoberať, pokiaľ by vás zaujímali, pozrite si dokumentáciu k API. Rozhranie MutableTreeNode rozširuje rozhranie TreeNode o metódy umožňujúce zmenu hodnoty uzla. Jeho metódami sa tu tiež nebudeme zaoberať, pretože triedou, ktorá nás zaujíma, je DefaultMutableTreeNode. Táto trieda implementuje spomínané rozhrania a pridáva ďalšie užitočné metódy na prácu s uzlom (napr. na získavanie informácií o súrodeneckých uzloch a pod.). Táto trieda umožňuje zapuzdrenie používateľského objektu, ktorý má byť pridružený k danému uzlu, čo je práve to, čo potrebujeme. Jednoduchým spôsobom môžete preto tvoriť akékoľvek zložité hierarchie, pripájať a odoberať uzly. Jeden z týchto uzlov vopred vyberieme ako koreňový a potom môžeme tento uzol nastaviť ako koreňový v dátovom modeli stromu.

Predpokladám, že teórie už máte viac než dosť, preto uvádzam nasledujúci príklad, ktorý je obmenou toho predchádzajúceho s použitím dátového modelu (uvádzam iba metódu init()):

  public void init() {
    DefaultMutableTreeNode root = new DefaultMutableTreeNode("root");
    DefaultMutableTreeNode ff = new DefaultMutableTreeNode("Fab Four");
 
    DefaultMutableTreeNode child = new DefaultMutableTreeNode("Paul");
    ff.add(child);
    child = new DefaultMutableTreeNode("John");
    ff.add(child);
    child = new DefaultMutableTreeNode("George");
    ff.add(child);
    child = new DefaultMutableTreeNode("Ringo");
    ff.add(child);
    root.add(ff);
 
    child = new DefaultMutableTreeNode("Help!");
    root.add(child);
    child = new DefaultMutableTreeNode("Revolver");
    root.add(child);
    child = new DefaultMutableTreeNode("Abbey road");
    root.add(child);
    child = new DefaultMutableTreeNode("Let it be");
    root.add(child);
 
    DefaultTreeModel model = new DefaultTreeModel(root);
 
    JTree tree = new JTree(model);
    tree.setRootVisible(true);
    getContentPane().add(tree);
  }

Výsledok bude rovnaký ako v predchádzajúcom príklade.

V tomto príklade je najprv vytvorený koreňový uzol identifikovaný ako root. Potom je vytvorený uzol "Fab Four", ku ktorému sú následne pridané ďalšie štyri uzly. Následne je uzol "Fab Four" pridaný ako dcérsky uzol koreňového uzla. K tomu sú potom pridané ešte ďalšie štyri dcérske uzly, ktoré sú listami.

Reťazce uvedené v konštruktoroch predstavujú zapuzdrené používateľské hodnoty, pridružené k daným uzlom. Na získanie týchto používateľských objektov (v tomto prípade reťazcov) slúži metóda getUserObject(), ktorú definuje trieda DefaultMutableTreeNode. Tá dokáže, samozrejme, zapuzdriť akýkoľvek objekt, nielen reťazec. Text vypísaný pri konkrétnom uzle je získaný metódou toString() zapuzdreného používateľského objektu, takže môžete jednoducho do stromu vkladať akékoľvek objekty a ich textovú reprezentáciu riadiť prekrytím tejto metódy. Pridružiť objekt uzlu je možné buď odovzdaním odkazu naň konštruktoru triedy DefaultMutableTreeNode, alebo pomocou metódy setUserObject(), definovanej v tejto triede.

Po vytvorení hierarchie uzlov je vytvorený dátový model DefaultTreeModel, ktorému je pomocou konštruktora odovzdaný odkaz na koreňový uzol tejto hierarchie. Potom sa už len vytvorí pohľad na tento dátový model, ktorý je reprezentovaný vizuálnym komponentom JTree, pričom dátový model je odovzdaný jeho konštruktoru. Pridaním tohto komponentu do kontajnera appletu sa celý proces končí.

Záver

Tentoraz sme sa venovali komponentom JTable a JTree a ich dátovým modelom, pričom šlo o pohľad naozaj len z rýchlika, hlavne čo sa týka komponentu JTree. Komplexnosť týchto komponentov mi neumožňuje venovať sa im tu do hĺbky, preto vám len odporúčam preštudovať API špecifikácie, prípadne knihu Professional Java Programming od Bretta Spella, vydanú vo Wrox Press, ktorá vyšla aj v českom preklade pod názvom Java – programujeme profesionálně. V nej sa môžete dozvedieť naozaj veľa o týchto dvoch komponentoch.

V nasledujúcej časti sa ešte trošku vrátime, pretože sa budeme venovať udalostnému modelu komponentu JTree. Dovtedy dovidenia.

 

Autor: Andrej Chu

Nechajte si posielať prehľad najdôležitejších správ emailom

Mohlo by Vás zaujímať

ITPro

Red Hat a Veracomp: Open source a cloudy

09.12.2016 12:05

Cieľom ďalšej z radu spoločných akcií firiem Red Hat a Veracomp Slovakia v Bratislave bola prezentácia produktov, služieb a stratégií týkajúcich sa najhorúcejších tém IT podpory biznisu s využitím naj ...

ITPro

Právne okienko

08.12.2016 12:02

1. Ako postupovať, ak obchodník nechce uznať reklamáciu tovaru objednaného z e-shopu? V takomto prípade môžu nastať v zásade dve situácie. Ak zákazník reklamuje tovar do 12 mesiacov od jeho kúpy, mož ...

ITPro

Red Hat Forum 2016: Otvorená digitálna transformácia

09.12.2016 11:51

Podujatím v Prahe pre región CENE (stredná a severná Európa) rezonovala komunitná atmosféra a snaha o spoluprácu podľa pravidla „každý s každým“. IT musí priniesť do digitálneho biznisu novú kultúru a ...

Žiadne komentáre

Vyhľadávanie

Kyocera - prve-zariadenia-formatu-a4-s-vykonom-a3

Najnovšie videá