miercuri, 24 iunie 2009

Utilizarea JTable in Swing - Partea 2


Prima parte a acesui tutorial o puteti gasi aici.


3. Selectarea elementelor tabelului

In mod default putem sa afisam si sa editam datele dintr-un tabel. Din punct de vedere al selectarii datelelor dintr-un tabel exista o serie de operatii pe care le putem efectua fara un efort prea mare. Pentru selectii trebui sa utilizam obiecte din clasa ListSelectionModel. Spre deosebire de marea majoritate a componetelor de interfata grafica, un JTable dispune de doua modele de selecte, unul pentru linii si unul pentru coloane.

Exemplul din de mai jos reprezinta o aplicatie care ne permite sa activam sau sa dezactivam diferitele tipuri de selectii aplicabile unui tabel (celula, rand si coloana). Pe masura ce selectati diferite randuri sau coloane, cele doua obiecte de tip JLabel vor indica indicii elementelor selectate.


Cea mai mare parte a codului este alocata pentru a realiaza interfata grafica. Din momentul in care aceasta este realizata vom atasa doi listeneri care vor intercepta evenimentele pentru selectia pe rand si coloana. Partea interesanta a codului este reprezentata urmareste orice ListSelectionModel si updateaza textul de pe etichete. (Indicii care sunt fisati pe aceste etichete sunt preluati folosind metoda getSelectedIndices() aflata in clasa SelectionDebugger). Din moment ce depindem doar de list selection model, putem folosi aceasi listener atat pentru randuri cat si pentru coloane.de ListSelectionListener, care este sub forma de inner class.



// LECSelectionExample.java
// Un simplu tabel populat cu numere naturale consecutive. Acest tabel permite
// modificarea modului de selectie. Puteti selecta fie celule, linii sau coloane
// pentru tabel.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class LECSelectionExample extends JFrame {

public LECSelectionExample( ) {
super("LEC Selection Model Test");
setSize(450, 350);
setDefaultCloseOperation(EXIT_ON_CLOSE);

TableModel tm = new AbstractTableModel( ) {
// Vom utiliza un simlu tabel de test needitabil populat cu
// numere naturale consecutive.
public int getRowCount( ) { return 10; }
public int getColumnCount( ) { return 10; }
public Object getValueAt(int r, int c) { return "" + (r+1)*(c+1); }
};

final JTable jt = new JTable(tm);

JScrollPane jsp = new JScrollPane(jt);
getContentPane( ).add(jsp, BorderLayout.CENTER);

// Setarea controalelor pentru selectie.
JPanel controlPanel, buttonPanel, columnPanel, rowPanel;

buttonPanel = new JPanel( );
final JCheckBox cellBox, columnBox, rowBox;
cellBox = new JCheckBox("Randuri", jt.getCellSelectionEnabled( ));
columnBox = new JCheckBox("Coloane", jt.getColumnSelectionAllowed( ));
rowBox = new JCheckBox("Randuri", jt.getRowSelectionAllowed( ));
cellBox.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent ae) {
jt.setCellSelectionEnabled(cellBox.isSelected( ));
columnBox.setSelected(jt.getColumnSelectionAllowed( ));
rowBox.setSelected(jt.getRowSelectionAllowed( ));
}
} );

columnBox.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent ae) {
jt.setColumnSelectionAllowed(columnBox.isSelected( ));
cellBox.setSelected(jt.getCellSelectionEnabled( ));
}
} );

rowBox.addActionListener(new ActionListener( ) {
public void actionPerformed(ActionEvent ae) {
jt.setRowSelectionAllowed(rowBox.isSelected( ));
cellBox.setSelected(jt.getCellSelectionEnabled( ));
}
} );

buttonPanel.add(new JLabel("Modul de selectie:"));
buttonPanel.add(cellBox);
buttonPanel.add(columnBox);
buttonPanel.add(rowBox);

columnPanel = new JPanel( );
ListSelectionModel csm = jt.getColumnModel( ).getSelectionModel( );
JLabel columnCounter = new JLabel("(Indicii coloanelor selectate)");
csm.addListSelectionListener(new SelectionDebugger(columnCounter, csm));
columnPanel.add(new JLabel("Coloanele selectate:"));
columnPanel.add(columnCounter);

rowPanel = new JPanel( );
ListSelectionModel rsm = jt.getSelectionModel( );
JLabel rowCounter = new JLabel("(Indicii randurilor selectate)");
rsm.addListSelectionListener(new SelectionDebugger(rowCounter, rsm));
rowPanel.add(new JLabel("Randuri selectate:"));
rowPanel.add(rowCounter);

controlPanel = new JPanel(new GridLayout(0, 1));
controlPanel.add(buttonPanel);
controlPanel.add(columnPanel);
controlPanel.add(rowPanel);

getContentPane( ).add(controlPanel, BorderLayout.SOUTH);
}

public static void main(String args[]) {
LECSelectionExample se = new LECSelectionExample( );
se.setVisible(true);
}

public class SelectionDebugger implements ListSelectionListener {
JLabel debugger;
ListSelectionModel model;

public SelectionDebugger(JLabel target, ListSelectionModel lsm) {
debugger = target;
model = lsm;
}
public void valueChanged(ListSelectionEvent lse) {
if (!lse.getValueIsAdjusting( )) {
// Se sare peste restul evenimentelor.
StringBuffer buf = new StringBuffer( );
int[] selection = getSelectedIndices(model.getMinSelectionIndex( ),
model.getMaxSelectionIndex( ));
if (selection.length == 0) {
buf.append("none");
}
else {
for (int i = 0; i < selection.length -1; i++) {
buf.append(selection[i]);
buf.append(", ");
}
buf.append(selection[selection.length - 1]);
}
debugger.setText(buf.toString( ));
}
}

// Aceastea metoda returneaza un array al indicilor selectati. Metoda
// nu poate intoarce valori care sunt null.
protected int[] getSelectedIndices(int start, int stop) {
if ((start == -1) || (stop == -1)) {
// Nimic selectat, asa ca vom intoarce un array gol
return new int[0];
}
int guesses[] = new int[stop - start + 1];
int index = 0;
// Parcurgere manuala
for (int i = start; i <= stop; i++) {
if (model.isSelectedIndex(i)) {
guesses[index++] = i;
}
}
int realthing[] = new int[index];
System.arraycopy(guesses, 0, realthing, 0, index);
return realthing;
}
}
}


Merita mentionat ca pentru aceasta aplicatia am fi putut prelua array-ul care continea indiici pentru randurile selectate din obiectul de tip JTable si array-ul care continea coloanele selectate din column model. Modivul pentru care nu am utilizat aceasta modalitate in exemplul nostru este ca am fi avut nevoie de listeneri separati pentru linii si pentru coloane.



4. Renderingul celulelor

Puteti construi proprii nostri render-eri pentru celulele dintr-un tabel. In mod standard dispuneti de rendereri pentru datele de tip Boolean (elemente de tip JCheckBox pentru afisare si editare), elemente de tip ImageIcon, valori numerice (elemente de tip JTextFiled). Pe langa aceastea puteti sa va creati si proprii rendereri pentru un anumit tip de clasa , pentru o anumita coloana, sau chiar pentru o anumita celula.


Interfata TableCellRenderer

Aceasta interfata ofera posibilitatea de a accesa componeta de rendering fara a specifica ce anume face aceasta componenta. Acest lucru functioneaza doarece un rederer in mod standard „lipeste” imaginea unui component de interfata grafica in pozitia indicata de noi. Singura metoda care este inclusa in aceasta interfata defineste, intializeaza si intoarce un component :


public abstract Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)

Aceasta metoda primeste o valoare, care poate fi de asemenea preluata specificandu-se celula din randul row, coloane column si tabelul table, si intoarce o componeta capabila sa deseneaze acea valoare in celula unui tabel.


Clasa DefaultTableCellRenderer

Pachetul javax.swing include un renderer standard care produce un JLabel pentru a afisa text in fiecare celula din tabel. Clasa JTable utilizeaza acest renderer pentru a afisa numere, icoane si obiecte. JTable creaza un renderer default, il pozitioneaza in locatia corecta si ii ataseaza o icoana, in functie de tipul de data. Obiectele de tip Object sunt transformate in String folosind metoda toString() si sunt afisate folosind un simplu JLabel. Elementele de tip numar sunt afisate folosind o aliniere la drepta, iar icoanele sunt afisate folosind o aliniere pe centru. Valorile de tip Boolean nu folosesc DefaultTableCellRenderer. In locul acestuia utilizeaza un clasa de rendering proprie care extinde JCheckBox.


Bineinteles ca ne putem construi si proprii nostrii rendereri pe baze clasei DefaultTableCellRenderer. Mai jos aveti un exemplu de renderer pe care il putem utiliza pentru a afisa dimensiunile unor fisiere pe harddisk. Aceast renderer pune o icoana reprezentand un semn de exclamare in fata oricarei date care este mai mare decat o anumita valoare pe care o dam ca praramentru in contructor.



// LECBigRenderer.java
// A renderer for numbers that shows an icon in front of big numbers
// Un renderer pentru numere care arata o imagine in fara numerelor mari.

import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;

public class LECBigRenderer extends DefaultTableCellRenderer {
double threshold;
Icon bang = new ImageIcon("bang.gif");

public LECBigRenderer(double t) {
threshold = t;
setHorizontalAlignment(JLabel.RIGHT);
setHorizontalTextPosition(SwingConstants.RIGHT);
}

public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row, int col)
{
if (value instanceof Number) {
if (((Number)value).doubleValue( ) > threshold) {
setIcon(bang);
}
else {
setIcon(null);
}
}
else {
setIcon(null);
}
return super.getTableCellRendererComponent(table, value, isSelected,
hasFocus, row, col);
}
}

Pentru a atasa acest renderer tabelului nostru, nu trebuie decat sa adaugam cateva linii de cod :
JTable jt = new JTable(fm);
// ...
jt.setDefaultRenderer(Number.class, new LECBigRenderer(100));


Figura de mai jos prezinta un exemplu care poate fi obtinut utilizandu-se acest renderer.



Clasa CellRenderePane

Aceasta clasa utilitar a fost construita pentru a impiedica rendererii sa propage apeluri catre repaint() si validate() pentru componete utilizand elemente de rendering precum JTree si JList. In cazul in care ati incercat sa va construti rendereri proprii pentru orice alt element de Swing, atunci ati putut observa ca nu puteti sa folositi acea clasa in mod direct. Acest pane este utilizat impreuna cu un renderer si diferitele metode paintComponent(), de care dispune, sunt utilizate pentru a reliza desenul efectiv. In mod normal nu trebuie sa modficati aceasta clasa.

Niciun comentariu:

Trimiteți un comentariu