JTable ed eventi

Stato
Discussione chiusa ad ulteriori risposte.

grifis78

Utente Silver
16 Ottobre 2007
2
0
0
60
Ciao a tutti ragazzi,

ho un problema al quale sto dietro oramai da giorni, ma senza soluzione di sorta. Ho creato una JTable e relativo listener ove vorrei che una volta che l'utente riempia una cella, in quella accanto compaia un altro valore (lo prendo da un database, ma qui, per comodità, usiamo una semplice variabile String). Questo valore lo inserisco con una chiamata al metodo setValueAt. Il problema è che tale metodo non fa altro che richiamare a sua volta il listener creando un loop infinito! Ho provato a mettere una variabile di controllo, ma nulla. Vi posto l'intero codice:

PHP:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.sql.*;


public class Vendita {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new VenditaFrame().setVisible(true);
            }
        });
    }
}


class VenditaFrame extends JFrame {
    
    private JScrollPane scrollPane;
    private JTextField rowTextField;
    private JTextField columnTextField;
    private JButton ensureVisibleButton;
    private JButton scrollButton;

    private int row=0, column=0;

    private JTextField campoQuantita; //campo col totale oggetti
    private JTextField campoTotale; //campo di testo col totale acquisti
    private int quantita = 0;

    public VenditaFrame() {
        super("Vendita al banco");

        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setSize(800, 600);

        campoQuantita = new JTextField(20);
        campoQuantita.setEditable(false);

        //dichiaro il tablemodel
        final DefaultTableModel data = new DefaultTableModel();

        //aggiungo al modello data le colonne
        data.addColumn("Codice");
        data.addColumn("Descrizione");
        data.addColumn("Q.tà");
        data.addColumn("Prezzo unitario");
        data.addColumn("Totale");
        //numero di righe
        data.setRowCount(99);

        //dichiaro la tabella con i dati contenuti in data
        final JTable table = new JTable(data);
        scrollPane = new JScrollPane(table);

        //table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        table.setRowHeight(30);
        table.setIntercellSpacing(new Dimension(8, 8));

        //aggiungo un listener
        data.addTableModelListener(new TableModelListener() {
            
            public void tableChanged(TableModelEvent e) {
                if(e.getType() == TableModelEvent.UPDATE) {
                    TableModel source = (TableModel)e.getSource();
                    int row = e.getFirstRow();
                    int col = e.getColumn();
                    Object value = source.getValueAt(row, col);

                    String prova="prova";//come fosse la query che mi restituisce il record del DB

                    data.setValueAt(prova, 3, 1); //E QUI CASCA L'ASINO....

                    quantita++;
                    campoQuantita.setText(Integer.toString(quantita));
                    //JOptionPane.showMessageDialog(table, cellaRicevuta);

                }//fine di if
            }//fine della inner class
        }//fine del metodo addTableModelListener
        );

        //indica di andare nella prossima cella e non nella riga sotto
        table.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "selectNextColumnCell");

        
        rowTextField = new JTextField(4);
        rowTextField.setText("1234");

        columnTextField = new JTextField(4);
        columnTextField.setText("4");

        ensureVisibleButton = new JButton("Bottone1");
        scrollButton = new JButton("Bottone2");

        JButton cancellaRigaBottone = new JButton("Cancella riga");
        cancellaRigaBottone.setToolTipText("Cancella l'intera riga");
        JButton cancellaTuttoBottone = new JButton ("Cancella tutto");
        JButton intermBottone = new JButton("Intermediario");
        JButton scontoBottone = new JButton("Sconto");
        JButton abbuonoBottone = new JButton("Abbuono");
        JButton fatturaBottone = new JButton("Fattura");
        JButton esciBottone = new JButton("Esci");

        //listener per exciBottone
        esciBottone.addActionListener(

            new ActionListener()
            {
               // pass query to table model
               public void actionPerformed( ActionEvent event )
               {
                  System.exit( 1 ); // terminate application
               } // end actionPerformed
            }  // end ActionListener inner class
         ); // end call to addActionListener

        JPanel pannelloAlto = new JPanel(new FlowLayout(FlowLayout.LEFT));
        pannelloAlto.add(cancellaRigaBottone);
        pannelloAlto.add(cancellaTuttoBottone);
        pannelloAlto.add(intermBottone);
        pannelloAlto.add(scontoBottone);
        pannelloAlto.add(abbuonoBottone);
        pannelloAlto.add(fatturaBottone);
        pannelloAlto.add(esciBottone);

        JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        controlPanel.add(new JLabel("Quantità"));
        controlPanel.add(campoQuantita);
        //controlPanel.add(rowTextField);
        controlPanel.add(new JLabel("Totale Euro"));
        controlPanel.add(columnTextField);
        controlPanel.add(ensureVisibleButton);
        controlPanel.add(scrollButton);

        getContentPane().add(pannelloAlto, BorderLayout.NORTH);
        getContentPane().add(scrollPane, BorderLayout.CENTER);
        getContentPane().add(controlPanel, BorderLayout.SOUTH);


    }//fine metodo VenditaFrame

    

} //fine classe Vendita

Per onor di cronaca, ho già richiesto aiuto al forum ufficiale di java SUN, ma mi hanno consigliato di togliere il listener ed inserire una cosa del genere:
PHP:
class MySumTableModel extends DefaultTableModel {
 
    @override
    public void setValueAt(value, row, column) {
          super.setValueAt(value, row, column);
          if (row != sumRow) {
               setValueAt(getColumnSum(column), sumRow, column);
          }
          
    }
 
    // prevent users to edit
    @override
    public boolean isEditable(...) {
          return row != sumRow;
    }
}
[/php
]

Potreste aiutarmi? Ci sto impazzendo dietro...
 
al momento l'unica soluzione che ho trovato è questa
PHP:
                    String prova="prova";//come fosse la query che mi restituisce il record del DB
if (data.getValueAt(3,1) != null && data.getValueAt(3,1).equals(prova))
	return;
                    data.setValueAt(prova, 3, 1); //E QUI CASCA L'ASINO....
 
la funzione che hai associato al listener si attiva ogni volta che la tabella viene modificata, sia manualmente che tramite codice.
quello non fa altro che:
1) controlla se è stata inserito qualche elemento nella cella incriminata (!=null)
è un controllo per evitare che si faccia .equals su un oggetto null (darebbe un'eccezione), perchè getValueAt restituisce null se non è presente niente in quella cella.
2) controlla se l'elemente inserito (se presente, attenzione che esiste la cortocircuitazione in java) è uguale a quello che vuoi che ci sia.
se è uguale significa che l'ho già aggiornato, quindi evito di riaggiornalo e mandarlo in loop
se è diverso significa che lo volevo aggiornare e quindi gli metto il valore che volevo.


in realtà la cosa migliore sarebbe "comunicare" quel valore, invece di rifare la richiesta al database, anche perchè nel frattempo potrebbe essere cambiato e ritornare in un loop (che dovrebbe finire si spera), ma non so come fare al momento
 
[ot]
R4z0r_Cr4sH ha detto:
ottima spiegazione whivel...non sapevo conoscessi java :D
ottima è un po esagerato.... ho studiato il java a scuola e anche all'università, oltre che da autodidatta[/ot]

cmq ripeto che secondo me c'è sicuramente una soluzione più efficente e priva di bug, che io ritengo essere presente in questa soluzione (indipendentemente dal contesto in cui si trova)
 
probabilmente si.Sarò sincero,il modo in cui è stata scritta la classe è totalmente differente da come solitamente faccio io (probabilmente son io a scriverlo sbagliato,anche se credo sia più ordinato e legibile) quindi appena posso (più tardi) ci do un'occhiata per bene...
 
se parli del fatto che i listener sono creati in maniera inline, effettivamente potrebbe essere meno leggibile, ma si tratta di pareri (perchè vedi subito a che oggetto è associato)
 
Grazie ragazzi, davvero.

@R4z0r: se hai ulteriori consigli te ne sarei grato. Io sto imparando soltanto da pochissimo tempo!

Prego soltanto di lasciare il 3d aperto perchè devo modificare ulteriormaente la JTable e sono certo verrò a rompere nuovamente! :)
 
@whivel: si,mi riferivo ai listener.Sono dell'idea che sia più legibile (e rendere la classe strutturata in modo migliore) utilizzando i listener come singoli metodi e non inline dato che per la JTable (dato che è un solo oggetto) potrebbe andare ma per i JButton?dovresti fare un listener inline per ogni bottone?non sarebbe meglio aggiungere l'ascoltatore all'intestazione della classe e fare un solo metodo actionlistener per tutti i botti?come anche la dichiarazione di due classi in un singolo file (in questo caso "Vendita" e "VenditaFrame"),non mi piace come cosa...
 
@R4z0r: si, in effetti ho inserito due classi insieme più che altro per rendere il codice funzionante subito, visto che sto facendo un mare di prove. Invece mi hai messo un pensiero con i listener dei pulsanti, visto che ognuno di loro ha una funzione diversa.. Ma ci penserò dopo perchè si è ripresentato il problema del loop! Scusate se rompo di nuovo...

In pratica, ho modificato quella che mi ha scritto whivel così:

PHP:
if (data.getValueAt(row, 1) != null && data.getValueAt(row, 1).equals(getCellaDatabase(value)))
                    return;
                    data.setValueAt(getCellaDatabase(value), row, 1);

ma va di nuovo in loop in quanto se nella JTable inserisco il valore "1", il metodo getCellaDatabase mi prende un dato che ha come id "1", ma poi, evidentemente, utilizza questo secondo per fare un ulteriore query! E mi mi torna, infatti, un errore nella query perchè non trova nessun "id".

Esempio pratico: parte l'applicazione, inserisco "1" nella cella 1,1 , il listener prende questo valore, fa la query, ed imposta (setValueAt) il record (mettiamo ad es. "grifis78") del database nella cella 1,2 ; questo, scatena di nuovo il listener che farà un'altra query col valore "grifis78", che non ha più senso. Questa seconda query non deve avvenire.

Ma porc... Spero di essermi spiegato bene! Prima di postare ho fatto diverse prove, ma nulla...

EDIT: in effetti, mi rendo conto che ho sbagliato a porre la domanda. Fin dal principio, dovevo mettere in evidenza si trattasse di dati presi da un database, e non semplificare (=sbagliare) considerando una vaiabile stringa. Infatti, getValueAt riprende il valore impostato con setValueAt rifacendo la query che adesso non ha più senso perchè non si tratta più del valore inizialmente impostato dall'utente.

A questo punto, immagino che l'unica discriminante possibile sia se esiste un modo per capire se l'input nella JTable sia avvenuto da tastiera oppure dal metodo getCellaDatabase. E' fattibile?

EDIT2: attenzione... ho tolto
PHP:
&& data.getValueAt(row, 1).equals(getCellaDatabase(value))
e sembra funzionare il tutto... Non vorrei parlare troppo però.. :blamg:

Scusate se sto utilizzando questo 3d a mò di mie memorie...
 
eliminadolo semplicemente aspetti che il valore venga impostato la prima volta e poi amen, qualsiasi modifica alla cella 1,1 non causerà più niente

ho pensato a 2 maniere: 1 però non funziona se modifichi la colonna X (la colonna che vai a settare automaticamente), la seconda usa una variabile (il che non è bello).

SOL. 1:
PHP:
int row = e.getFirstRow();
int col = e.getColumn();
if(col==1)
	return

SOL 2:
PHP:
private JButton scrollButton;
private boolean edit=false;
PHP:
int row = e.getFirstRow();
int col = e.getColumn();
	if(edit)
	{
		edit=false;
		return;
	}
PHP:
String prova=Double.toString(Math.random()*100.0); 
//come fosse la query che mi restituisce il record del DB
edit=true;
data.setValueAt(prova, 3, 1); //E QUI CASCA L'ASINO....


:EDIT:
c'è la seconda soluzione può essere anche scritta così (più sicura):
PHP:
private JButton scrollButton;
private boolean edit=false;
PHP:
int row = e.getFirstRow();
int col = e.getColumn();
	if(edit)
		return;
PHP:
String prova=Double.toString(Math.random()*100.0); 
//come fosse la query che mi restituisce il record del DB
edit=true;
data.setValueAt(prova, 3, 1); //E QUI CASCA L'ASINO....
edit=false
 
Grazie ancora Whivel, i tuoi consigli sono stati preziosissimi! Ho utilizzato la seconda opzione. Adesso vorrei fare una cosa che immagino sia semplicissima, ma, nonostante abbia letto la guida java.sun per JTable, non ho trovato soluzione: una volta che la seconda cella è stata riempita, vorrei che il focus passasse in automatico alla terza cella, mentre invece rimane su quella imposta automaticamente. Sai come si fa?

Spero di non abusare adesso... :)
 
non sia abusa mai quando si tratta di cose interessanti o cmq valide. appena ho un po di tempo ti rispondo (se non lo fa qualcuno prima)
 
Perfetto whivel, gentilissimo. Sto cercando anche nella documentazione allegato a netBeans, ma ancora nulla..

EDIT: risolto!

PHP:
table.setCellSelectionEnabled(true);
...
table.changeSelection(row, column, false, false);
table.requestFocus();

Sto odiando JTable... :)
 
PHP:
data.setValueAt(prova, row, 1); //E QUI CASCA L'ASINO....
edit=false;
col=col+1;
if(col==1)
	col=col+1;
if(col==table.getColumnCount())
{
	row=row+1;
	col=0;
}
table.changeSelection(row,col,false,false);

PHP:
//indica di andare nella prossima cella e non nella riga sotto
        table.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "MySelectNextColumnCell");

	table.getActionMap().put("MySelectNextColumnCell", new MyAction());

PHP:
class MyAction extends AbstractAction
{

		public void actionPerformed(ActionEvent e)
				{

					JTable y = (JTable)e.getSource();

					int row = y.getEditingRow();
                    			int col = y.getEditingColumn();
					
					col=col+1;
					if(col==1)
						col=col+1;
					if(col==y.getColumnCount())
					{
						row=row+1;
						col=0;
					}
					//interrompe l'editing sulla cella corrente
					y.editCellAt(-1,-1);
					y.changeSelection(row,col,false,false);
				}
	
}


se vedi c'è un codice ripetuto dentro la nuova classe MyAction e dentro il Listener della tabella. Questo perchè quello dentra la classe si attiva quando tu hai dato il focus alla cella e premi un tasto modificandone il contenuto, menetre il secondo quando clicchi 2 volte sulla cella, iniziando a modificarne il contenuto.

NOTA:
se editi qualsiasi colonna, il valore nella colonna 1 (la seconda) viene modificato.
Se vuoi evitare questo e faro accadere solo editando la colonna 0 (la prima)
PHP:
int col = e.getColumn();
		
if(col!=0) return;
if(edit)
	return;


:EDIT:
comunque stavo vedendo che esiste un evento che si chiama
editingStopped(ChangeEvent e)
This tells the listeners the editor has ended editing
in CellEditorListener, che forse è più appropriato per quello che serve a te (cambiare valore in una cella dopo che hai modificato un'altra)


:EDIT2:
la cosa è un po complicata, la soluzione più semplice per usare CellEditorListener credo sia il subclassing, ma vabbè se funziona così
 
allora,ho riscritto la classe (con stesse variabili ed istruzioni) ma come l'avrei impostata io:
Codice:
import javax.swing.event.*;
import javax.swing.table.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
import java.sql.*;

public class MVendita extends JFrame implements ActionListener,TableModelListener{

    private JTextField rowTextField=new JTextField(4);
    private JTextField columnTextField=new JTextField(4);
    private JButton ensureVisibleButton=new JButton("Bottone1");
    private JButton scrollButton= new JButton("Bottone2");
    private JTextField campoQuantita=new JTextField(20); //campo col totale oggetti
    private JTextField campoTotale; //campo di testo col totale acquisti
    private int quantita = 0;
    
    final DefaultTableModel data = new DefaultTableModel();
    final JTable table = new JTable(data);
    private JScrollPane scrollPane=new JScrollPane(table);;
    
    JButton cancellaRigaBottone = new JButton("Cancella riga");
    JButton cancellaTuttoBottone = new JButton ("Cancella tutto");
    JButton intermBottone = new JButton("Intermediario");
    JButton scontoBottone = new JButton("Sconto");
    JButton abbuonoBottone = new JButton("Abbuono");
    JButton fatturaBottone = new JButton("Fattura");
    JButton esciBottone = new JButton("Esci");

    public static void main(String[] args){
            new MVendita();
    }

    public MVendita(){
        rowTextField.setText("1234");
        columnTextField.setText("4");
        cancellaRigaBottone.setToolTipText("Cancella l'intera riga");
        campoQuantita.setEditable(false);
        
        //aggiungo al modello data le colonne
        data.addColumn("Codice");
        data.addColumn("Descrizione");
        data.addColumn("Q.tà");
        data.addColumn("Prezzo unitario");
        data.addColumn("Totale");
        //numero di righe
        data.setRowCount(99);
        
        //table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        table.setRowHeight(30);
        table.setIntercellSpacing(new Dimension(8,8));
        
        //indica di andare nella prossima cella e non nella riga sotto
        table.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "selectNextColumnCell");

        JPanel pannelloAlto = new JPanel(new FlowLayout(FlowLayout.LEFT));
        pannelloAlto.add(cancellaRigaBottone);
        pannelloAlto.add(cancellaTuttoBottone);
        pannelloAlto.add(intermBottone);
        pannelloAlto.add(scontoBottone);
        pannelloAlto.add(abbuonoBottone);
        pannelloAlto.add(fatturaBottone);
        pannelloAlto.add(esciBottone);

        JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        controlPanel.add(new JLabel("Quantità"));
        controlPanel.add(campoQuantita);
        //controlPanel.add(rowTextField);
        controlPanel.add(new JLabel("Totale Euro"));
        controlPanel.add(columnTextField);
        controlPanel.add(ensureVisibleButton);
        controlPanel.add(scrollButton);

        getContentPane().add(pannelloAlto, BorderLayout.NORTH);
        getContentPane().add(scrollPane, BorderLayout.CENTER);
        getContentPane().add(controlPanel, BorderLayout.SOUTH);
        
        esciBottone.addActionListener(this);
        
        table.getModel().addTableModelListener(this);
    
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setSize(800,600);
        setVisible(true);
        show();
    }

    public void actionPerformed(ActionEvent e){
        Object source=e.getSource();
        if(source==esciBottone){
            System.exit(1);
        }
    }
    public void tableChanged(TableModelEvent e){
        int row=e.getFirstRow();
        int column=e.getColumn();
        TableModel model=(TableModel)e.getSource();
        
        String prova="prova";
        
        //E QUI CASCA L'ASINO.... 
        quantita++;
        campoQuantita.setText(Integer.toString(quantita));
        //JOptionPane.showMessageDialog(table, cellaRicevuta);
    }
    
}
saltando il problema della JTable.Per quel poco che ho potuto vedere il problema sta nel fatto che il metodo setValueAt() che va a scrivere nella cella accanto richiama in automatico l'ascoltatore ecco perchè va in loop (anche se non credo sia un loop,ma un refresh di tutte le celle).Ho utilizzato più volte l'oggetto JTable anche con programmi che prendono dati dal un database e posso dirti che io creo un vettore ed una matrice:
Codice:
String[] column_list;
Object[][] cells;
successivamente ricavo la grandezza da dargli (quante righe e quante colonne) e successivamente popolo entrambi per poi passargli a JTable con:
Codice:
table=new JTable(cells,column_list);
in questo modo le modifiche le vado a fare su "column_list" e "cells" e non sulla JTable diretta...
 
Grazie R4z0r e Whivel!

Quella di utilizzare Array o vettori era un'idea che sapevo essere migliore di quella che ho implementato, ma, non so perchè, ogni volta che ho avuto a che fare con questi costrutti ho sempre cercato l'alternativa! Beh, in realtà è prigrizia.. Figurati che, essendo un piccolo sviluppatore php, ogni volta che ho avuto a che fare con dati da ordinare in un certo modo, ho sempre fatto affidamento ad un database anche quando non ce n'era bisogno piuttosto che "alleggerire" il tutto utilizzando array e simili.

Speriamo adesso che il vostro intervento possa darmi una scossa. Grazie ancora, vedrò di migliorare il mio codice in questo senso.
 
ma il codice che ti ho dato funziona?

grifis78 ha detto:
Perfetto whivel, gentilissimo. Sto cercando anche nella documentazione allegato a netBeans, ma ancora nulla..

EDIT: risolto!

PHP:
table.setCellSelectionEnabled(true);
...
table.changeSelection(row, column, false, false);
table.requestFocus();

Sto odiando JTable... :)
vorrei sbagliare, ma se non ricordo male questo non funziona se tu selezioni la cella (senza cliccare 2 volte) e inizia a scriverci (oppure non funziona quando clicchi 2 volte, ti "appare" l'editing della cella e scrivi).

:EDIT:
leggi un po qua^^ forse ti spazza via la pigrizia lol
http://www.infernet.ws/showthread.php?tid=16514
 
whivel, funziona perfettamente anche se non ci clicchi 2 volte (per mia fortuna! :) )

@Razor: beh, credo di si! In ogni caso, essendo alle prime armi, lascio tutto com'è e continuo il mio progetto. Poi magari torno indietro per ottimizzare!
 
Stato
Discussione chiusa ad ulteriori risposte.