7. fejezet - Grafikus felhasználói felületek készítése

Tartalom

Swing felületek felépítése
Komponens hozzáadása a tartalompanelhez
Szöveges komponensek
Listák és legördülő listák
Táblázatok
Elrendezéskezelők (Layout menedzserek)
Tervezési minták a Swing keretrendszerben

A Java nyelv korai időszakában (már az 1.0-ás verzióban) megjelent egy grafikus felhasználói felületek (graphical user interfaces, GUI) készítésére szolgáló programkönyvtár (library). Ennek eredeti tervezési céljai között az szerepelt, hogy segítségével egy minden platformon jól kinéző felhasználói felületet lehessen létrehozni, azonban ezt a célt nem sikerült elérni. Ez a programkönyvtár az absztrakt ablakozó eszközkészlet (abstract windowing toolkit, AWT) volt, amelynek segítségével minden platformra egyformán középszerűen kinéző felületeket lehetett létrehozni, ráadásul mindemellett sok kényelmetlen korlátozást is tartalmazott: többek között csak négyféle betűtípus használatára volt lehetőség és nem lehetett elérni az egyes operációs rendszerekben létező kifinomult felületelelemeket sem. Ezen túlmenően az AWT programozási modellje is nagyon kényelmetlen és nem eléggé objektumorientált volt.

A Java 1.1 már egy sokkal letisztultabb objektumorientált megközelítésű AWT eseménymodellt hozott (a JavaBeans komponensmodellel együtt), azonban az átalakulás azzal lett teljes, hogy a Java 2 (JDK 1.2) kialakításakor létrejött a Java Foundation Classes (JFC), amelynek a grafikus felhasználói felületekkel foglalkozó részét Swingnek nevezték, de emellett lehetőséget biztosít a kinézet és a felhasználói élmény (vagyis a look and feel) cserélhetőségére, API-kat biztosít az akadálymentesítés és kétdimenziós grafikák készítéséhez, és egy nemzetköziesítési (internationalization, röviden i18n) megoldás segítségével lehetővé teszi többnyelvű alkalmazások létrehozását is. Könnyen használható és könnyen érthető JavaBeans komponenseket biztosít a programozók számára, amelyekkel akár fogd és vidd (drag and drop) módszerrel, akár kézzel leprogramozva megfelelő felhasználói felületek hozhatóak létre.

Ebben a fejezetben a Swing keretrendszer alapelemeit mutatjuk be. A legtöbb Swing komponens a javax.swing csomagban illetve annak alcsomagjaiban található. A Swing keretrendszer rengeteg API-t, komponenst és egyéb lehetőségeket rejt. Éppen ezért fontosnak tartjuk megjegyezni, hogy e fejezetnek nem célja sem a Swing komponenseinek átfogó bemutatása, sem pedig az egyes tárgyalt komponensek metódusainak mindenre kiterjedő bemutatása. A Swing programkönyvtára hatalmas, ezért a fejezet célja csupán annyi, hogy betekintést nyújtson az alapvető elemekbe és fogalmakba, és kiindulópontként szolgáljon a további ismeretszerzéshez. Ebben nagy segítséget nyújthat a Swing tutoriál [ SwingTutorial ], illetve a témában íródott számos szakkönyv valamelyike [ SCHILDT2006 ].

Egy Swingben írt felület alapvetően komponensek hierarchiáiból és eseménykezelőkből épül fel. A legfelső szinten lévő komponensek, a tárolóobjektumok adják a többi objektum keretét. Ezek általában ablakok vagy dialógusablakok, amelyeken panelek helyezkednek el. A panelekhez további paneleket vagy egyéb komponenseket (például gombokat, szövegmezőket, táblázatokat, fastruktúrákat, legördülő listákat) adhatunk hozzá.

Swing felületek felépítése

A Swingben írt felületek valójában komponensek hierarchiái, amelyeket a legmagasabb szinten lévő komponensekbe, konténerekbe helyezhetünk el. Minden Swing komponens konténer is egyben, amelybe újabb komponensek tehetők, de mivel azok szintén konténerek, így újabb komponenseket rendelhetünk hozzájuk. A javax.swing.JComponent osztály a korábbi grafikusfelület-programozási API egyik központi osztályának, a java.awt.Container-nek a leszármazottja.

A legmagasabb szinten lévő komponensek, az ablakok a hierarchiák gyökérelemei. Három olyan konténerosztály létezik, amely egy gyökérelemet reprezentálhat: a JFrame, a JDialog és a JApplet.

Minden GUI komponens csak egy konténerben szerepelhet egyszerre: ha áthelyezzük egy másik konténerbe, akkor az előzőből automatikusan törlődik. Azonos típusú objektumokból több is szerepelhet egy konténerben.

Menüsort csak legfelső szinten lévő komponensekhez adhatunk. A menüsor a tartalompanelen kívül helyezkedik el.

7.1. ábra - Példa komponenshierarchiára [SwingTutorial]

Példa komponenshierarchiára [SwingTutorial]

A legfelső szinten elhelyezkedő komponensek közül több is képezhet gyökérelemet. Például, amennyiben egy alkalmazás több felületből áll: egy JFrame (főablak) és több JDialog (dialógusablakok) objektumból, akkor ezek mindegyike gyökérelem. Appletek esetében azonban csak egy JApplet objektum alkothat gyökeret.

Komponens hozzáadása a tartalompanelhez

Miután létrehozunk egy új komponenst vagy komponenshierarchiát (amely maga is komponens), hozzáadjuk a keret objektum tartalompaneljéhez.

frame.getContentPane().addComponent(jTabbedPane1);

Ez az utasítás egy fülekkel ellátott lapokból álló panelt ad hozzá a tartalompanelhez.

A modern integrált fejlesztői környezetekben nem szükséges kézzel programoznunk a grafikus felhasználói felületeket. Az IDE olyan segédeszközöket (például palettát) biztosít, amelyek használatával a szerkesztési területre húzhatók a komponenesek, a környezet pedig legenerálja a megfelelő Java-kódot a háttérben.

Megjegyzés

A legelterjedtebb integrált fejlesztőeszközökben lehetőségünk van a felhasználói felület drag-and-drop módon történő fejlesztésére. A Netbeans a Swing GUI Builder (korábban Matisse) nevű felületépítő eszközkészletet tartalmazza, míg az Eclipse-hez a legelterjedtebb ilyen eszköz a WindowBuilder nevű plugin, amely az Eclipse for Java Developers változatban előre telepítve van, az egyéb Eclipse változatokban pedig az Eclipse Marketplace segítségével pluginként telepíthető. Az IntelliJ Idea is rendelkezik Swing GUI Designer-rel, és az Oracle JDeveloper is beépítetten tartalmaz ilyen eszközt.

7.2. ábra - Az Eclipse WindowBuilder GUI programozást segítő palettája

Az Eclipse WindowBuilder GUI programozást segítő palettája

A JComponent osztály

Minden Swing komponens a JComponent osztály leszármazottja, így örökli annak adattagjait és metódusait. A leszármazott osztályok egyedi jellemzőin túl vannak olyan tulajdonságok, amelyekkel minden Swing komponensnek rendelkeznie kell. Ezek a tulajdonságok a közös ősben, a JComponent osztályban találhatók, és hét területet ölelnek fel, amelyek a következők:

  • A komponens megjelenítésének testreszabása (keret, előtér, háttér, betűk).

  • A komponens állapotának lekérdezése és beállítása (engedélyezés, láthatóság, szerkeszthetőség).

  • A Figyelő (Observer) és a Parancs (Command) tervezési mintát megvalósító eseménykezelők (különböző listener objektumok) hozzárendelése a komponenshez.

  • A komponens megjelenítése (kirajzolása).

  • Azon komponenshierarchia kezelése, amelynek a komponens a gyökéreleme.

  • A tartalmazott komponensek elhelyezkedésének (layout) kezelése.

  • Információk és beállítások a komponens méretével és pozíciójával kapcsolatban.

A következő szakaszokban néhány alapvető komponenst ismerhetünk meg. Az összetettebb komponenseknél bepillanthatunk a mögöttük álló modellek és eseménykezelők működésébe is.

Szöveges komponensek

Az első csoportban az egyszerű szövegmezők találhatók. Ezek egysoros, rövidebb szöveget kezelő komponensek. A második és a harmadik csoportba a szövegterületek tartoznak, amelyek hosszabb, többsoros szöveges adatok megjelenítésére és szerkesztésére használhatók. A szövegmezők és –területek értékét a getText metódussal kérdezhetjük le, illetve a setText metódus segítségével állíthatjuk be.

7.3. ábra - Szöveges komponensek osztályozása [SwingTutorial]

Szöveges komponensek osztályozása [SwingTutorial]


String username = jTextField1.getText();
String password = new String(jPasswordField1.getPassword());
jTextField1.setText("");
jPasswordField1.setText("");

Megjegyzés

Jelszót tároló szövegmező esetében a getPassword metódus használata javasolt a getText metódus helyett, azonban az String helyett char[]-öt ad vissza.

Listák és legördülő listák

A listák és legördülő listák egy vagy több választási lehetőséget kínálnak a felhasználónak. Egyszerű listákat akkor célszerű használni, ha a felületen sok hely van, vagy fontos, hogy minden pillanatban minél több elem látsszon. Legördülő listákat (comboboxokat) akkor célszerű alkalmazni, ha kevés a hely a felületen, mert ezek a komponensek összezárt állapotban mindössze egyetlen sornyi helyet foglalnak el.

A listák és legördülő listák listamodellekkel dolgoznak, amelyek az adatokat tartalmazzák.

A következő kódrészlet bemutatja, hogyan készíthetünk saját listamodellt, valamint a Swing keretrendszer által biztosított alapértelmezett listamodellek használatát.

A modelleket a JList komponens és a JComboBox komponens setModel metódusával adjuk át a komponenseknek.

private List<String> categories;

A saját listamodell létrehozása például névtelen osztállyal történhet. Miután a kategóriákat tartalmazó listát feltöltjük az adatbázisból, átadjuk azt a listamodellnek. A felüldefiniált getSize metódus a kategóriákat tartalmazó lista méretével tér vissza, a getElementAt metódus pedig ennek a listának az adott elemét kérdezi le és adja át.

private AbstractListModel categoryListModel;

List<String> categories = new ArrayList<>();

for (List<Object> cols : entityManager.select("select distinct category from products")) {
    categories.add("" + cols.get(0));
}
    
categoryListModel = new javax.swing.AbstractListModel() {
    @Override
    public int getSize() { return categories.size(); }
        
    @Override
    public Object getElementAt(int i) { return categories.get(i); }
};
    
jList2.setModel(categoryListModel);

A következő kódrészletben a legördülő listának egy alapértelmezett modellt adunk át, amely az általunk beállított értékeket tartalmazza.

jComboBox1.setModel(new javax.swing.DefaultComboBoxModel(new String[]{"ÜGYFÉL", "ADMINISZTRÁTOR", "VEZETŐ"}));

A kiválasztási modell

A listák és legördülő listák egy ListSelectionModel objektumot használnak elemeik kiválasztásának módjához. Három kiválasztási mód lehetséges:

Egyszeres kiválasztás

7.4. ábra - Egyszeres kiválasztás

Egyszeres kiválasztás


Egy időben csak egy listaelem választható ki. Ha a felhasználó egy másik elemet jelöl ki, az előző automatikusan deszelektálódik.

Egyszeres intervallum kiválasztás

7.5. ábra - Egyszeres intervallum kiválasztás

Egyszeres intervallum kiválasztás


Több, egymást közvetlenül követő elem kiválasztása. Amikor a felhasználó egy új intervallumot választ, az előzőleg kijelölt elemek deszelektálódnak.

Többszörös intervallum kiválasztás

7.6. ábra - Többszörös intervallum kiválasztás

Többszörös intervallum kiválasztás


Ez az alapértelmezett kiválasztási mód. Az elemek bármely kombinációja választható. A felhasználó maga deszelektálja az elemeket, ha szükséges.

A kiválasztási modellt a setSelectionMode metódus segítségével állíthatjuk be egy lista komponensen. A lista kiválasztási modellt táblázatobjektumokra is alkalmazhatjuk.

Egyszeres kiválasztási mód beállítása.

jList2.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
...
jTable1.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

Eseménykezelők

7.7. ábra - Eseménykezelők [SwingTutorial]

Eseménykezelők [SwingTutorial]


A Swing eseménymodellje hatékony és rugalmas. Bármely eseménytípust tetszőlegesen sok forrásból felismerhet több eseményfigyelő objektum (event listener). Egy esemény figyelésére egy objektum is beállítható, de egy listener objektum minden forrás minden eseményét is figyelheti, valamint egy esemény figyelésére egyetlen forrásból több listener objektum is ügyelhet.

Minden esemény egy objektum, amely információkat tartalmaz az eseményről és az esemény forrásobjektumáról. Az események forrása legtöbbször komponens vagy modell, de bármely objektumtípus funkcionálhat eseményforrásként.

A Swing az eseményeket csoportosítja is: beszél alacsony szintű és szemantikus eseményekről. Az alacsony szintű események főként az ablakozó rendszer eseményei, mint amilyenek például a közvetlenül a felhasználótól érkező billentyűzet- és egéresemények. A szemantikus események ezeknél magasabb szintűek, és felhasználói bemenet váltja ki: például rákattint egy gombra. Jellemző, hogy egy szemantikus esemény mögött alacsony szintű események egész sorozata húzódik meg: egy gombnyomáshoz, mint szemantikus eseményhez az egérkurzor pozícionálására, az egérgomb lenyomására és felengedésére is szükség van (ezek mind alacsony szintű műveletek). Ráadásul ugyanazt a szemantikus eseményt több alacsony szintű eseménysoorozattal is elérhetjük, hiszen a gombnyomás szemantikus esemény pusztán a billentyűzet segítségével is kiváltható: a TAB billentyűvel kiválasztva a gombot a szóköz illetve az Enter billentyű lenyomásával.

A szemantikus eseményeket négy osztály reprezentálja:

  • az ActionEvent, amely ezek közül a leggyakoribb, gombnyomáskor, menüválasztáskor, listaelem-kiválasztáskor, illetve a szövegmezőn Enter leütésekor keletkezik ilyen esemény;

  • az AdjustmentEvent, amely egy görgetősáv (scroll bar) használata során következik be;

  • az ItemEvent akkor áll elő, ha a felhasználó jelölőnégyzetek segítségével vagy listaelemekből kiválaszt valamit;

  • míg végül a TextEvent akkor jön létre, ha egy szövegmező vagy szövegterület tartalma megváltozik.

Az alacsony szintű események osztályai az alábbiak:

  • ComponentEvent, amely amellett, hogy komponensek átméretezése, mozgatása, megjelenítése illetve elrejtése során következik be, az összes alacsony szintű esemény ősosztálya is;

  • KeyEvent, amely egy billentyű lenyomása és felengedése esetén generálódik;

  • MouseEvent, amely az egérkurzor mozgatása, az egyes egérgombok lenyomása és felengedése, és vonszolás során jön létre;

  • FocusEvent, amelynek a segítségével arról értesülhhetünk, ha egy komponens megkapta vagy éppen elvesztette a fókuszt;

  • WindowEvent, akkor generálódik, ha egy ablakot aktiváltak, deaktiváltak, ikon állapotúra kicsinyítettek, ikon állapotúról felnagyították, vegy éppen bezárták; és végül

  • ContainerEvent, ami egy-egy komponens konténerhez adásakor vagy onnan való törlésekor jön létre.

Ha csak lehet, a szemantikus eseményekre kell feliratkoznunk, nem pedig az alacsony szintűekre. Ennek több előnye is van: egyrészt így tudjuk a kódunkat a leginkább hordozhatóvá és robusztussá tenni, másrészt pedig így akkor is értesülünk egy eseményről (például egy nyomógombra kattintásról), ha azt nem az elvárt módon végezték. Egy nyomógomb megnyomását például csak egérrel érhetjük el, hanem a billentyűzettel is. Ha ekkor a szemantikus esemény helyett csupán arra iratkoztunk volna fel, hogy egy adott területen (ahol a gomb van) történik-e kattintás és felengedés egérgomb segítségével, akkor a billentyűzet segítségével végzett gombnyomásról bizony lemaradnánk.

A következő táblázat a komponensek és a hozzájuk rendelhető figyelő objektumok típusát tartalmazza.

7.1. táblázat - Komponensek és figyelőik [SwingTutorial]

KomponensActionListenerCaretListenerChangeListenerDocumentListener,UndoableEditListenerItemListenerListSelectionListenerWindowListenerTovábbi listener fajták
button
 
 
   
check box
 
 
   
color chooser  
     
combo box
   
   
dialog      
 
file chooser
       
frame      
 
list     
 ListData
password field
 
    
radio button
 
 
   
table     
 TableModel, TableColumnModel, CellEditor
text area 
 
    
text field
 
    


Egy tevékenységfigyelő (action listener) implementálásával megadhatjuk, hogy mi történjen, ha a felhasználó egy bizonyos tevékenységet végez. A felhasználó által végzett tevékenység lehet például egy gomb megnyomása, egy listaelem kiválasztása, vagy az Enter billentyű megnyomása egy szövegmezőn. Mindennek az az eredménye, hogy egy actionPerformed üzenet kerül elküldésre minden olyan tevékenységfigyelő objektumnak, amely fel van iratkozva az esemény forráskomponensére.

Egy tevékenységfigyelő implementálása a következő lépésekből áll:

  1. Egy eseménykezelő osztály deklarálása, amely implementálja az ActionListener interfészt.

    public class MyActionListener implements ActionListener {
        ...
    }
  2. Az interfész actionPerformed metódusának implementálása. A példában a kombóbox segítségével kiválasztott elemet (egy szerepkört) a currentRole nevű változónak adjuk értékül.

    public void actionPerformed(java.awt.event.ActionEvent evt) {
        JComboBox cb = (JComboBox) evt.getSource();
        currentRole = (String) jComboBox1.getSelectedItem();
    }

    Megjegyzés

    Amennyiben az eseménykezelőt több eseményforráshoz is csatoljuk (regisztráljuk), és szeretnénk attól függő feldolgozást végezni, hogy melyik volt az az eseményforrás, amelyen az esemény ténylegesen bekövetkezett és amely miatt az eseménykezelő aktivizálódott, akkor az ActionEvent objektumból tudjuk mindezt kinyerni, ahogyan a fenti példa is mutatja (evt.getSource()). Erre általában nincs szükségünk, mert sokszor vagy csak egy eseményforrással dolgozunk, vagy ha több is van belőlük, nem feltétlenül van szükség forrásfüggő feldolgozásra. Ekkor a paramétert gyakorlatilag figyelmen kívül hagyjuk.

  3. Az osztály egy példányának egy vagy több komponens figyelésére történő feliratkoztatása. Ez tulajdonképpen nem más, mint a Megfigyelő (Observer) minta megjelenése, amikor a megfigyelő (ez esetben a MyActionListener példány) regisztrálja magát a megfigyeltnél (ez a jComboBox1 változónévvel hivatkozott kombóbox).

    jComboBox1.addActionListener(new MyActionListener());

Ezt követően, ha a felhasználó elvégez egy bizonyos tevékenységet, a komponens elindít egy tevékenységeseményt. Ez az ActionListener interfészt megvalósító objektum actionPerformed metódusának meghívását eredményezi. A metódus egyetlen paramétere egy ActionEvent objektum, amely információt adhat az eseményről és annak forráskomponenséről.

Megjegyzés

A tevékenységfigyelőt gyakran névtelen osztályként valósítjuk meg. Különösen így van ez akkor, ha csak egyetlen felületelemhez tartozik az eseménykezelő, hiszen akkor elég csak egyetlen helyen, a megfigyeltnél történő regisztációkor (addActionListener hívás) hivatkozni.

Táblázatok

7.8. ábra - Táblázat beágyazott ComboBox objektumokkal

Táblázat beágyazott ComboBox objektumokkal


A JTable osztály objektumainak segítségével táblázatos adatokat tudunk megjeleníteni és kezelni. A táblázatok oszlopokból és sorokból állnak, amelyeknek metszetében a cella található. A táblázat adatait a kódban egy kétdimenziós objektumtömbként vagy Vector-ok Vector-aként adhatjuk meg (adatok paraméter).

Object[][] adatok = {...};
Object[] oszlopnevek = {...};
JTable tablazat = new JTable(adatok, oszlopnevek);

Első konstruktor: public JTable(Object[][] adatok,
              Object[] oszlopnevek);

Második konstruktor: public JTable(Vector adatok,
              Vector oszlopnevek);

Egy táblázat objektum sorain és oszlopain szűrési és rendezési műveleteket is végezhetünk, amelyekhez saját implementációt adhatunk.

Kiválasztás

Egy táblázaton összetettebb módon hajthatunk végre kijelöléseket, mint listák esetében, habár az összetettebb kijelölések visszavezethetők az egyszerűbbekre. A táblázatban sorokat, oszlopokat, és cellákat jelölhetünk ki. A kiválasztás módja lehet összefüggő, valamint nem összefüggő többszörös, vagy egyszeres.

Modellek

A legtöbb Swing komponens mögött áll egy modell. Például egy nyomógomb (JButton) objektum rendelkezik egy ButtonModel interfészt megvalósító objektummal, amely a gomb állapotáról tárol információt. Némelyik komponenshez több modell is hozzá van rendelve, például egy JList objektum egy ListModel-t használ a tartalma tárolására és egy ListSelectionModel-t az aktuális kijelölések számára.

Legtöbbször nem szükséges tudnunk ezekről a modellekről, elegendő magát a komponenst programoznunk.

Mindazonáltal, szükség van modellekre az adatok rugalmas tárolásának és elérésének érdekében. Például, egy egyedi tábla esetében javasolt saját táblamodell implementációt megadnunk, ha az alapértelmezett modell nem felel meg az elvárásoknak.

A modellek nem csupán tárolják az adatokat, hanem automatikusan továbbítják az adatokon bekövetkezett változásokat a figyelő objektumok felé. Például egy listaelem hozzáadásánál a komponens helyett a listamodell megfelelő metódusa hívódik meg. Ha változás következik be, a listamodell értesíti a megjelenítő komponenst és a feliratkozott figyelőket, így a felület azonnal frissül.

Fontos

A Swing egy módosított modell–nézet–vezérlő (Model–View–Controller) minta mentén épül fel, ahol a modell jól elkülöníthető a nézettől és a vezérlőtől, azonban a megjelenítés és a vezérlő egymástól nem teljesen szétválasztható.

Táblamodell létrehozása

Minden táblázatobjektum mögött áll egy táblamodell, amely a táblázat adatait kezeli. A táblamodell objektumnak implementálnia kell a TableModel interfészt. Ha nem rendelünk táblamodellt egy táblázat komponenshez, automatikusan hozzárendelődik egy úgynevezett DefaultTableModel típusú objektumpéldány.

7.9. ábra - Táblázat és táblamodelljének kapcsolata [SwingTutorial]

Táblázat és táblamodelljének kapcsolata [SwingTutorial]


public class MyTableModel extends AbstractTableModel {
    protected Object[][] tableData;
    protected List<String> columnNames;

A legördülő lista, amelynek segítségével a felhasználó kiválaszthatja, hogy a könyvet a polcra vagy a kosárba teszi-e.

private final JComboBox moreSelection;

A táblázat sorainak számát visszaadó metódus.

@Override
public int getRowCount() {
    return this.tableData.length;
}

A táblázat oszlopainak számát visszaadó metódus.

@Override
public int getColumnCount() {
    return this.columnNames.size();
}

Az adott cellában található értéket visszaadó metódus.

@Override
public Object getValueAt(int rowIndex, int colIndex) {
    return this.tableData[rowIndex][colIndex];
}

Adott oszlop nevét visszaadó metódus.

@Override
public String getColumnName(int colIndex) {
    return this.columnNames.get(colIndex);
}

Ez a metódus az adott oszlop típusát határozza meg. Ha nem implementáljuk a metódust, akkor minden oszlop értéke szövegként jelenik meg. A példa azt mutatja be, hogy mit tegyünk akkor, ha a logikai értékek megjelenítését jelölőnégyzet (checkbox) segítségével szeretnénk megvalósítani.

@Override
public Class getColumnClass(int colIndex) {
    Class c = getValueAt(0, colIndex).getClass();
    if (c.equals(Boolean.class)) {
        if (colIndex != selectionColIndex) {
            return Object.class;
        }
    }
    return c;
}

Ez a metódus megmondja, hogy az adott cella szerkeszthető-e. Csak akkor szükséges implementálni, ha a táblázat szerkeszthető.

@Override
public boolean isCellEditable(int rowIndex, int colIndex) {
    if (colIndex < this.selectionColIndex) {
        return false;
    } 
    else {
        return true;
    }
}

Az adott cellában található értéket beállító metódus. Csak akkor szükséges implementálni, ha a táblázat adatai változhatnak.

@Override
public void setValueAt(Object value, int rowIndex, int colIndex) {
    this.tableData[rowIndex][colIndex] = value;
    fireTableCellUpdated(rowIndex, colIndex);
    selectionValuesById.put(this.tableData[rowIndex][0], value);
}

Megjegyzés

A fireTableCellUpdated metódus hívásának hatására az összes érdeklő figyelő értesítést kap arról, hogy a [rowIndex, colIndex] cella tartalma frissült.

Események kezelése

Egyazon táblamodell objektumra akár több figyelővel is feliratkozhatunk, amelyek így a modell adatain (állapotán) bekövetkezett változásokról értesítést kapnak. A táblamodell figyelőinek a TableModelListener interfészt kell implementálniuk.

public class MyTableModelListener implements TableModelListener {
    public MyTableModelListener() {
        ...              
        table.getModel().addTableModelListener(this);
    }

    public void tableChanged(TableModelEvent e) {
        //a kiválasztás első sora
        int row = e.getFirstRow();
        //a kiválasztott oszlop
        int column = e.getColumn();
        //az esemény forrása
        TableModel model = (TableModel)e.getSource();
        //a kiválasztott oszlop neve
        String columnName = model.getColumnName(column);
        //a cellában található érték
        Object data = model.getValueAt(row, column);
        ...
    }    
}

Amennyiben a felület osztályából elérjük a táblát, nincs szükség TableModelEvent objektumra. Ekkor egy egyszerű actionPerformed metódus kezeli az eseményt.

private void jButton14ActionPerformed(java.awt.event.ActionEvent evt) {
    int selectedRow = jTable1.getSelectedRow();

Amennyiben a táblázat rendezve is van, az alábbi metódust is meg kell hívnunk a helyes index megtalálásához.

    selectedRow = jTable1.convertRowIndexToModel(selectedRow);
    // Könyv azonosítója (ISBN)
    idOfSelectedProduct = (String)jTable1.getModel().getValueAt(selectedRow, 0);
} 

Az adatok megváltozásának eseménye csak úgy váltható ki, ha a táblamodell tudatában van annak, hogy hogyan kell egy TableModelEvent objektumot előállítani. Ez bonyolult feladat, azonban a DefaultTableModel osztály megfelelő implementációt biztosít hozzá. Így ha saját implementációt írunk, a DefaultTableModel osztály kiterjesztése javasolt. Amennyiben az alapértelmezett táblamodell osztály mégsem felel meg alaposztályként, az AbstractTableModel osztályt kell kiterjesztenünk. Ebben az esetben saját osztályunknak elegendő a következő metódusok egyikét hívni, amikor egy külső forrás módosítja a táblamodell adatait.

MetódusVáltozás
fireTableCellUpdated Egy cella módosítása
fireTableRowsUpdated Sorok módosítása
fireTableDataChanged A teljes táblázat adatainak változása (csak az adatok)
fireTableRowsInserted Új sor beszúrása
fireTableRowsDeleted Sorok törlése
fireTableStructureChanged A táblázat adatainak és szerkezetének módosítása

A következó kódrészlet egy fireTableCellUpdated metódus hívását mutatja be.

@Override
public void setValueAt(Object value, int rowIndex, int colIndex) {
    this.tableData[rowIndex][colIndex] = value;
    fireTableCellUpdated(rowIndex, colIndex);
    selectionValuesById.put(this.tableData[rowIndex][0], value);}

Elrendezéskezelők (Layout menedzserek)

Egy elrendezéskezelő vagy elhelyezéskezelő a LayoutManager interfészt implementáló objektum, amely a komponensek méretét és pozícióját határozza meg egy tárolón belül.A Swing komponensek alapértelmezett elrendezéskezelővel rendelkeznek, amely például panelek esetében a FlowLayout, míg tartalompaneleknél a BorderLayout. Az alapértelmezett elrendezéskezelők leválthatók, erre azonban legtöbbször nincs szükség.

JPanel panel = new JPanel(new BorderLayout());
... 
Container contentPane = frame.getContentPane();
contentPane.setLayout(new FlowLayout());

Amennyiben nem használunk elhelyezéskezelőt, abszolút pozícionálást kell végeznünk, amely értelmében minden komponensnek explicit módon kell megadnunk a méretét és a pozícióját a tárolón belül. Ezek az értékek rögzítettek, amelyek például az ablak átméretezésekor sem frissülnek. Éppen ezért ez kerülendő.

  • BorderLayout: a tartalompanelek (content pane) alapértelmezett elrendezéskezelője, így az összes legfelső szintű konténerben (ablakkeretben, dialógusablakban és appletben) ezt használhatjuk alapból. A rendelkezésre álló területet öt részre osztja fel: fölső, alsó, jobb oldali, bal oldali és középső, ahogyan az az ábrán is látható.

    7.10. ábra - BorderLayout [SwingTutorial]

    BorderLayout [SwingTutorial]


  • BoxLayout: a komponenseket egy sorba vagy egy oszlopba helyezi, figyelembe véve a komponens által igényelt maximális méretet.

    7.11. ábra - BoxLayout [SwingTutorial]

    BoxLayout [SwingTutorial]


  • CardLayout: segítségével olyan felület valósítható meg, ahol a különböző időpontokban különféle komponensek jelennek meg. Tulajdonképpen fülek segítségével elért lapokként gondolhatnuk rá, ahol sokszor tényleges fülek helyett egy combobox segítségével válthatunk az egyes lapok között.

    7.12. ábra - CardLayout [SwingTutorial]

    CardLayout [SwingTutorial]
    CardLayout [SwingTutorial]


  • FlowLayout: a JPanel-ek alapértelmezett elrendezéskezelője, sorfolytonosan tölti ki a rendelkezésre álló teret. Ha egy sorban már nincs elegendő helye egy elhelyezendő komponens számára, akkor új sort kezd.

    7.13. ábra - FlowLayout [SwingTutorial]

    FlowLayout [SwingTutorial]


  • GridLayout: a komponensek számára azonos méretet határoz meg egy n×m-es rácshálóban.

    7.14. ábra - GridLayout [SwingTutorial]

    GridLayout [SwingTutorial]


  • GridBagLayout: nagyon fejlett és rugalmas elrendezéskezelő, a GridLayout továbbfejlesztéseknét jött létre. A komponenseket szintén egy rácsháló alapján helyezi el, de megengedi, hogy egy-egy komponens több sort, illetve oszlopot is elfoglaljon. A rácsháló egyes sorai akár eltérő magasságúak, oszlopai pedig akár eltérő szélességűek is lehetnek. Nagyfokú rugalmassága mellett az egyik legnehezebben használható elrendezéskezelő is egyben.

    7.15. ábra - GridBagLayout [SwingTutorial]

    GridBagLayout [SwingTutorial]


  • GroupLayout: a grafikus felhasználói felületet drag-and-drop módszerrel öszeállító integrált fejlesztői keretrendszerek számára jött létre. Külön-külön foglalkozik a vízszintes és a függőleges elrendezéssel, vagyis minden komponens helyét kétszer kell definiálni: egyszer vízszintesen, egyszer pedig függőlegesen kell elhelyezni.

    7.16. ábra - GroupLayout [SwingTutorial]

    GroupLayout [SwingTutorial]


  • SpringLayout: szintén IDE-támogatásként jött létre. Lehetővé teszi, hogy az egyes komponensek szélei között pontos kapcsolatokkal írhassuk le komponenseink elhelyezkedését. Definiálható segítségével például, hogy adott komponens bal széle és egy másik komponens jobb széle között mekkora legyen a távolság (amely akár dinamikusan is számítható).

    7.17. ábra - SpringLayout [SwingTutorial]

    SpringLayout [SwingTutorial]
    SpringLayout [SwingTutorial]


A megfelelő elhelyezési stratégia kiválasztása

Az elhelyezéskezelőt nem szükséges kézzel beállítanunk, az integrált fejlesztői keretrendszerek biztosítanak olyan eszközöket, amelyek segítségével ez egyszerűen elvégezhető.

Amennyiben úgy döntünk, hogy mégis segédprogram nélkül, kézzel állítjuk be a layout menedzsereket, a következő rész nyújt segítséget annak eldöntésében, hogy milyen esetben mely elrendezéskezelőt érdemes használni.

  • Amennyiben a lehető legnagyobb helyen szeretnénk a komponenst megjeleníteni, akkor a GridLayout vagy a BorderLayout a megfelelő választás, egy komponens esetében. Több komponensnél érdemesebb a GridBagLayout-ot használni.

  • Amennyiben néhány komponenst akarunk egy sorban megjeleníteni, átméretezés nélkül, akkor a FlowLayout, a BoxLayout vagy a SpringLayout a megfelelő választás.

  • Amennyiben néhány komponenst akarunk megjeleníteni egyforma méretben oszlopokba és sorokba rendezve, akkor a GridLayout szerinti elrendezést érdemes választanunk.

  • Amennyiben néhány komponenst szeretnénk egy sorban vagy egy oszlopban megjeleníteni, változó térközzel, igazítással és méretezéssel, akkor a BoxLayout-ot érdemes használni.

  • Amennyiben rendezett oszlopokat szeretnénk megjeleníteni egy űrlap elrendezéséhez hasonló módon, ahol a címkék az egyes szövegmezők megnevezésére szolgálnak, a SpringLayout a legmegfelelőbb választás.

  • Amennyiben összetett elrendezést kívánunk használni nagyszámú komponenssel, valamely rugalmas elrendezéskezelőt érdemes választanunk, például a GridBagLayout-ot vagy a SpringLayout-ot. Egy másik módszer, ha az összetett elrendezést szétválasztjuk kisebb panelekre, amelyeken önálló elrendezést állítunk be önálló kezelővel, ez általában véve is jó stratégia a bonyolultság csökkentésére.

Elrendezéskezelők működése

Az elhelyezéskezelők alapvetően két dolgot tesznek: kiszámolják a tároló minimális, maximális és preferált méretét, és elrendezik a tároló beágyazott komponenseit. Ezek a komponenesek ráadásul rendelkezhetnek saját elrendezéskezelővel, amelyek előnyt élveznek a tartalmazó komponens elrendezéskezelőjével szemben, vagyis a specifikusabb komponensre előírt elhelyezés felülírja az általánosabbra előírtat.

Egy konténer állapota lehet érvényes vagy érvénytelen. Ahhoz, hogy egy konténer érvényes állapotba kerüljön, minden gyermekén végbe kell mennie az elrendezésnek, és így érvényes állapotba kell kerülnie. Az elrendezés elvégzését és a komponens érvényes állapotba juttatását a Container osztály validate metódusa végzi.

Miután egy komponens elkészült, még érvénytelen állapotban van. Első alkalommal a Window.pack metódus hívása érvényesíti az ablakot, és végzi el az alkomponensek elrendezését.