PDA

View Full Version : [java] filtrare tabella


guldo76
23-04-2006, 15:45
Ciao
Sto cercando di capire come si potrebbe fare per filtrare una tabella in java (swing).
Cioè:
ho un oggetto JTable, creato in base a un arraylist; vorrei filtrare il contenuto del JTable in base alla selezione effettuata in un JComboBox esterno.
In base alla selezione effettuata nel JComboBox, andrebbero tolte dalla JTable le righe corrispondenti.
Si può fare?
Ho visto che nella classe JTable è previsto il sort della tabella, ma del filter non ho trovato traccia...

L'unica cosa che mi è venuta in mente è distruggere e ricreare la tabella tutte le volte, modificando la fonte dati; ma è terribile...

Grazie :D

guldo76
24-04-2006, 13:25
:help:

PGI-Bis
24-04-2006, 13:42
Funziona anche cambiando il modello ma, in accordo all'architettura MVC (o SMA), dovresti agire sulla vista (JTable).

JTable ha tre metodi ad hoc:

getRowCount()
getColumnCount()
getValueAt(int row, int col)

Per definizione questi metodi si rifanno al TableModel. La tabella "standard" mostra tante righe e tante colonne quante sono quello nel modello e restituisce i valori corrispondenti alle righe del modello.

Tutto ciò di cui hai bisogno è determinare, a seguito dell'operazione di filtraggio, quali righe del modello siano da scartare. Restituirai quindi attraverso getRowCount un numero di righe pari al numero di righe nel modello meno quelle scartate dal filtro. Concordemente farai sì che il metodo getValueAt tenga conto della "sparizione" di queste righe.

E il gioco è fatto :).

guldo76
24-04-2006, 20:14
Grazie mille della risposta :)
Però ho ancora un po' di problemi... :(
Mi da più di una:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException

Quello che ho fatto è grossomodo questo:

public class LANTable extends JTable {
private ArrayList _righeOk = new ArrayList();
private ArrayList _source;
public LANTable(ArrayList connList) {
super();
this.setModel(new ConnTableModel(connList));
// [...]
_righeOk.clear();
for (int i = 0; i < _source.size(); i++)
_righeOk.add(new Integer(i));
}
public void Filter(String nomeRete) {
Iterator iter = _source.iterator();
int i = 0;
_righeOk.clear();
while (iter.hasNext()) {
LANConnection x = (LANConnection) iter.next();
if (x.get(x.getKeysArray()[0]).toString().equals(nomeRete))
_righeOk.add(new Integer(i));
}
}
class ConnTableModel extends AbstractTableModel {
public ConnTableModel(ArrayList connList) {
_source = connList;
}
// [...]
public int getRowCount() {
return _righeOk.size();
}
public Object getValueAt(int arg0, int arg1) {
Object risultato = null;
if (_righeOk.contains(new Integer(arg0))) {
LANConnection x = (LANConnection) _source.get(arg0);
risultato = x.get(x.getKeysArray()[arg1]);
}
return risultato;
}
// [...]
}
}

public class LANFrame extends JFrame {
public LANFrame(ArrayList list) {
super("Connessioni LAN");
final ArrayList myList = list;
// [...]
final LANTable myTab = new LANTable(myList);
final JComboBox jcmb = new JComboBox();
jcmb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
myTab.Filter(jcmb.getSelectedItem().toString());
}
});
TreeSet ts = new TreeSet();
String[] k = ((LANConnection)myList.get(0)).getKeysArray();
for (int i = 0; i < myList.size(); i++) {
ts.add(((LANConnection)myList.get(i)).get(k[0]));
}
Iterator iter = ts.iterator();
while (iter.hasNext()) {
jcmb.addItem(iter.next());
}
// [...]
}
}

Poi nel main istanzio un ArrayList e lo passo al costruttore di LANFrame.
Dov'è che sbaglio? :confused:

Inoltre ho un po' di problemi a ricreare la tabella rileggendo l'arraylist da un file (serialized); è corretto valorizzare un attributo di una classe in una sua classe interna? Cioè, nel mio esempio, è corretto valorizzare "_source" all'interno del costruttore della classe "ConnTableModel"?

Grazie :D

PGI-Bis
25-04-2006, 11:42
E' anche possibile che esista un caso in cui una classe interna possa assegnare un valore ad un campo della classe che la contiene ma nel caso di specie è bizzarro.

Sembrerebbe più coerente definire il modello ConnTableModel come tipo primario, dire:

public class LANTable ... {
...
public LANTable(TableModel model) {
super(model);
}
}

e generare una "normale" tabella:

ConnTableModel model = new ConnTableModel(...);
LANTable table = new LANTable(model);

Le viste dipendono dalla dichiarazione di un modello ma non dalla sua definizione. Per chiarezza, itendo per dichiarazione TableModel e per definizione un qualsiasi oggetto compatibile con TableModel (ad esempio proprio ConnTableModel).

Per l'eccezione NullPointerException occorre controllare la traccia stampata sulla console. Ad occhio non mi sembra di aver visto puntatori a null nel codice (ma non sono mai stato un'aquila :D).

guldo76
25-04-2006, 17:08
Tutto ciò di cui hai bisogno è determinare, a seguito dell'operazione di filtraggio, quali righe del modello siano da scartare. Restituirai quindi attraverso getRowCount un numero di righe pari al numero di righe nel modello meno quelle scartate dal filtro. Concordemente farai sì che il metodo getValueAt tenga conto della "sparizione" di queste righe.
Ciao.
L'ho rivisto praticamente daccapo, seguendo le tue indicazioni successive, ma questo ancora non mi riesce.
Evidentemente come lo faccio io non va bene :(
Per ora è così:
public class LANTableModel extends AbstractTableModel{

private ArrayList<LANConnection> _source;
private ArrayList<Integer> _righeOk;

public LANTableModel(ArrayList<LANConnection> connList){
_source = connList;
_righeOk = new ArrayList<Integer>();
for(int i=0; i<_source.size(); i++)
_righeOk.add(new Integer(i));
}

public int getRowCount(){
return _righeOk.size();
}

public Object getValueAt(int row, int column){
Object risultato = null;
LANConnection x = null;
if(_righeOk.contains(new Integer(row))){
x = _source.get(row);
risultato = x.get(x.getKeysArray()[column]);
}
return risultato;
}
[...]
}
E poi quando la istanzio provo subito a "filtrarla":
LANTableModel model = new LANTableModel(connList);
LANTable tab = new LANTable(model);
JScrollPane scroll = new JScrollPane(tab);
this.add(scroll);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
model.getRigheOk().remove(0);
Cosa c'è di sbagliato?

Grazie :D

PGI-Bis
26-04-2006, 13:02
Puoi benissimo applicare il filtro al modello (TableModel) ma quello che intendevo io era filtrare la vista: i dati restano quelli che sono, attraverso il filtro se ne vedono solo alcuni. Una cosa del genere:

public interface FiltroTabella {
int getRowCount();
int getColumnCount();
Object getValueAt(int row, int col);
void apply(JTable t);
}

public class TabellaFiltrabile extends JTable {
private FiltroTabella filtro;

public void setFiltro(FiltroTabella t) {
filtro = t;
if(filtro != null) filtro.apply(this);
}

public int getRowCount() {
return filtro == null ? super.getRowCount() : filtro.getRowCount();
}

public int getColumnCount() {
return filtro == null ? super.getColumnCount() : filtro.getColumnCount();
}

public Object getValueAt(int r, int c) {
return filtro == null ? super.getValueAt(r, c) : filtro.getValueAt(r, c);
}
}

Sono anch'io dell'idea che il filtro deva tener traccia delle delle righe valide: una specie di mappa tra gli indici delle righe visibili ed il loro corrispondente del modello.

guldo76
26-04-2006, 21:13
Ti ringrazio molto della pazienza e della disponibilità; quello che mi proponi è interessante :D

Però c'è una cosa a monte che vorrei capire.
Per il momento prendiamo per buono il mio sistema di scegliere le righe "buone" all'interno della classe TableModel; supponiamo che faccia così:
public LANTableModel(ArrayList<LANConnection> connList){
_source = connList;
_righeOk = new ArrayList<Integer>();
for(int i=0; i<_source.size(); i++){
if(i != 1) // NE ESCLUDO UNA DI PROPOSITO
_righeOk.add(new Integer(i));
}
}
// [...]
public int getRowCount(){
return _righeOk.size();
}

public Object getValueAt(int row, int column){
Object risultato = null;
LANConnection x = null;
if(_righeOk.contains(new Integer(row))){
x = _source.get(row);
risultato = x.get(x.getKeysArray()[column]);
}
return risultato;
}
così il metodo getValueAt restitusce un null nel caso della riga di indice 1.
Questo genera una NullPointerException.

Come devo implementare allora questo metodo, per fargli "saltare" la riga in questione?

Grazie :D

PGI-Bis
27-04-2006, 12:06
Forse abbiamo visto l'ArrayList in modo diverso :D

Quando scorri il modello per stabilire quali righe siano valide e quali no e immetti nell'ArrayList gli indici delle righe valide, l'ArrayList diventa una mappa di valori (X, Y = arrayList.get(N)), con X indice della riga richiesta dalla tabella e Y indice della riga "reale" nel modello.

Il tuo getValueAt diviene pertanto:

public Object getValueAt(int row, int column) {
int realRow = _righeOk.get(row);
return model.getValueAt(realRow, column);
}

Quel "model.getValueAt(realRow, column)" deve essere inteso nel senso di "il valore che sarebbe stato restituito se non fosse stato applicato alcun filtro".

guldo76
28-04-2006, 21:53
Grazie mille.
Quello che non capivo proprio era cosa restituire al posto della riga scartata dal filtro.
Ho risolto così:
public Object getValueAt(int row, int column){
Object risultato = null;
LANConnection x = null;
if(row < _righeOk.size()){
row = Integer.valueOf(_righeOk.get(row));
}
x = _source.get(row);
risultato = x.get(x.getKeysArray()[column]);
return risultato;
}
Ora ho problemi con la grafica :(
La tabella è in un jscrollpane, che filtrandola sclera, rimane traccia dello spazio che avanza...
Ho cercato di capire come funziona il repaint, ma non ci riesco... :fagiano:
Sono andato prima per tentativi, con la tabella, il jscrollpane e il frame, poi ho cercato di leggere questo (http://java.sun.com/products/jfc/tsc/articles/painting/index.html#swing), ma non c'ho ricavato un granché... :stordita:

Grazie di nuovo :D

PGI-Bis
29-04-2006, 02:21
Dovresti invocare, dopo aver applicato il filtro, il metodo revalidate() della tabella (JTable) incriminata.

guldo76
29-04-2006, 10:51
Fantastico!
Non so come ringraziarti!
:D