PDA

View Full Version : [JAVA] Aggiornare immediatamente il JPanel


pumax84
27-10-2008, 18:27
Ciao a tutti.

Nell'applicazione che sto ultimando avrei bisogno di far accendere una JLabel di Please Waiting mentre si sta eseguendo l'algoritmo e farla spegnere al termine del calcolo.

Ho implementato il tutto in questo modo:

pleaseWaitLabel.setEnabled(true);
repaint();
revalidate();

//Istruzioni che richiamano vari metodi per eseguire l'algoritmo principale

pleaseWaitLabel.setEnabled(false);
repaint();
revalidate();


Il problema è che l'aggiornamento del pannello non avviene immediatamente. A quanto pare esegue prima l'algoritmo e dopo aggiorna il pannello, di conseguenza si accende e si spegne immediatamente.

Come potrei risolvere? Esiste in alternativa una finestra da far comparire (stile JOptionPane) e chiudere automaticamente alla fine dell'eleborazione?

Grazie anticipatamente.

Ciao,
Francesco

banryu79
27-10-2008, 18:55
Guarda qua che fa revalidate() (http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JComponent.html#revalidate()).
Inoltre non dovrebbe essere neccessario chiamarlo.

Hai già provato così:

pleaseWaitLabel.setEnabled(true);
repaint();

//Istruzioni che richiamano vari metodi per eseguire l'algoritmo principale

pleaseWaitLabel.setEnabled(false);
repaint();


P.S.: il codice che viene eseguito alla riga "//Istruzioni che richiamano vari metodi per eseguire l'algoritmo principale" viene eseguito dallo stesso Thread in cui ti trovi o hai lanciato un altro thread per occuparsene?
Puoi postare uno spezzone maggiore del codice?

pumax84
27-10-2008, 19:21
We, ciao banryu! :)

Guarda qua che fa revalidate() (http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JComponent.html#revalidate()).
Inoltre non dovrebbe essere neccessario chiamarlo.
L'ho richiamato, oltre al metodo repaint(), perchè per alcune funzioni funziona meglio l'uno o l'altro. Ad esempio quando aggiungo attività (non so se ricordi grossomodo l'applicazione) il metodo repaint() non mi aggiorna il pannello, mentre revalidate() si. Al contrario quando le rimuovo solo revalidate() mi lascia degli artefatti sul pannello, mentre repaint() lo pulisce completamente.



P.S.: il codice che viene eseguito alla riga "//Istruzioni che richiamano vari metodi per eseguire l'algoritmo principale" viene eseguito dallo stesso Thread in cui ti trovi o hai lanciato un altro thread per occuparsene?
Puoi postare uno spezzone maggiore del codice?
Stesso Thread.

Ecco altro codice (posto l'intera azione del listener):
if(e.getSource() == computeButton) {

int numRepetitions = -1;
int acyclic = 0;

try {

if (getRepetitions() > 0)
numRepetitions = getRepetitions();
else
JOptionPane.showMessageDialog(null,
"<html><body>The format is not valid!<br>" +
"Please enter a positive integer<br>" +
"and less than 2147483648.</body></html>", "Error",
JOptionPane.ERROR_MESSAGE);
}

catch(NumberFormatException exception) {

JOptionPane.showMessageDialog(null,
"<html><body>The format is not valid!<br>" +
"Please enter a positive integer<br>" +
"and less than 2147483648.</body></html>", "Error",
JOptionPane.ERROR_MESSAGE);

}

pleaseWaitLabel.setEnabled(true);

//Refresh MainPanel
repaint();
revalidate();

if(numRepetitions > 0) {

ArrayList<ArrayList<Object>> dataActArray = new ArrayList<ArrayList<Object>>();
dataActArray = getDataAct();

if(dataActArray.get(0).size() > 1) {

startSimulations = System.currentTimeMillis();

MCSimulation mcs = new MCSimulation(numRepetitions, dataActArray);
acyclic = mcs.topologicalSort();

if(acyclic == 1) {
System.out.println("Array (prima colonna) ordinato topologicamente:");
mcs.printSortArray();
mcs.makeInNodes();
System.out.println();
System.out.println("Matrice inNodes:");
mcs.printInNodes();
System.out.println();
System.out.println("Array inDegree:");
mcs.printInDegree();
System.out.println();
System.out.println();
mcs.simulations();
stopSimulations = System.currentTimeMillis();
} else {
stopSimulations = System.currentTimeMillis();
JOptionPane.showMessageDialog(null,
"<html><body>The following activities make a cyclical project!!!<br>" +
"Please check all precedences in the activities!!!</body></html>", "Error",
JOptionPane.ERROR_MESSAGE);
}

timeSpentField.setText(""+((double)(stopSimulations-startSimulations)/1000));

} else {
System.out.println("Error: ci sono state delle eccezioni!");
}

}

pleaseWaitLabel.setEnabled(false);

//Refresh MainPanel
repaint();
revalidate();

}

banryu79
28-10-2008, 10:19
Dato che lo spezzone di codice postato è quello contenuto in un Listener significa che il Thread in cui viene processato è l'EDT.

L'EDT (Event Dispatching Thread) è il Thread di AWT che processa eventi AWT, per l'appunto. AWT usa un modello di painting a thread singolo dove tutti gli aggiornamenti della grafica vengono processati in un singolo Thread.

L'EDT è l'unico thread valido da cui poter aggiornare lo stato visibile dell'interfaccia grafica utente. Questo implica, tra l'altro, che aggiornare la grafica da altri thread diversi dall'EDT può essere causa di comportamenti anomali e funzionamenti non corretti dell'interfaccia grafica.

C'è poi anche il rovescio della medaglia da considerare. E cioè far eseguire all'EDT anche troppo lavoro (per esempio l'esecuzione di un algoritmo o dell'altro codice "che porta via tempo") col rischio di appesantire la velocità di risposta dell'interfaccia grafica.

Venendo al punto: dato che subito prima di eseguire l'algoritmo vuoi abilitare una JLabel disbilitata, e che nel farlo poi chiami di fila a repaint() un revalidate(), e che leggendo la documentazione a proposito di revalidate() notiamo che dice:

public void revalidate()

Supports deferred automatic layout.

Calls invalidate and then adds this component's validateRoot to a list of components that need to be validated. Validation will occur after all currently pending events have been dispatched


l'esecuzione successiva del codice del tuo algoritmo tiene impegnato il thread EDT.
Potrestri provare a inserire il codice relativo al tuo algoritmo nel metodo run() di un altro thread creato al volo.
Puoi fare una cosa del genere:

Thread algoThread = new Thread() {
public void run()
{
// codice algoritmo
}
};
algoThread.start();


Questo dovrebbe "liberare" l'EDT dall'esecuzione dell'algoritmo e permettergli di visualizzare in tempo la JLabel (anche se, come avrai capito, l'aggiornamento non è *istantaneo*).


Ah, prima volevo farti una domanda: perchè la JLabel segli di abilitarla (setEnable), invece di renterla semplicemente visibile e invisibile (setVisible)?

pumax84
28-10-2008, 13:20
Capisco, sei stato molto chiaro.

Il problema che mi si presenta però è che avendo variabili dichiarate esternamente (come numRepetitions) mi suggerisce di dichiararle "final" (cosa che naturalmente non posso fare) affinchè il thread possa "usufruirne".

Come posso aggirare questo problema?

Altrimenti devo inglobare dentro il thread tutto il codice che viene eseguito cliccando sul pulsante Compute (e non solo l'algoritmo) TRANNE le istruzioni per la JLabel...

P.S. setVisible è infatti la soluzione finale. Per provare però ho volutamente utilizzato setEnabled, in modo da esser sicuro (vedendo sia la parte spenta che accesa) che tutto funzionasse a dovere... cosa che fin'ora non è accaduto! :D

banryu79
28-10-2008, 14:19
P.S. setVisible è infatti la soluzione finale. Per provare però ho volutamente utilizzato setEnabled, in modo da esser sicuro (vedendo sia la parte spenta che accesa) che tutto funzionasse a dovere... cosa che fin'ora non è accaduto! :D
Ma scusa, prova direttamente con setVisible() no? Perchè complicarti la vita :D ?
Prova con setVisible() e senza la chiamata a revalidate().

@EDIT:
in ogni caso devi delegare ad un altro thread (esterno quindi all'EDT) l'esecuzione dell'algoritmo.

pumax84
29-10-2008, 15:31
Ma scusa, prova direttamente con setVisible() no? Perchè complicarti la vita :D ?
Prova con setVisible() e senza la chiamata a revalidate().
Si, era più che altro per vedere all'inizio (quando stavo costruendo l'interfaccia) l'aspetto che avrebbe avuto quella JLabel lì. Avevo impostato all'inizio il setEnabled ed era rimasto così. :D


@EDIT:
in ogni caso devi delegare ad un altro thread (esterno quindi all'EDT) l'esecuzione dell'algoritmo.
Ho inglobato tutto dentro il thread, tranne il setVisible(true) iniziale. Funziona alla grande. ;) Grazie mille di nuovo!

Ecco il codice:
if(e.getSource() == computeButton) {

Thread algorithmThread = new Thread() {

@Override
public void run()
{

int numRepetitions = -1;
int acyclic = 0;

try {

if (getRepetitions() > 0)
numRepetitions = getRepetitions();
else
JOptionPane.showMessageDialog(null,
"<html><body>The format is not valid!<br>" +
"Please enter a positive integer<br>" +
"and less than 2147483648.</body></html>", "Error",
JOptionPane.ERROR_MESSAGE);
}

catch(NumberFormatException exception) {

JOptionPane.showMessageDialog(null,
"<html><body>The format is not valid!<br>" +
"Please enter a positive integer<br>" +
"and less than 2147483648.</body></html>", "Error",
JOptionPane.ERROR_MESSAGE);

}

if(numRepetitions > 0) {

ArrayList<ArrayList<Object>> dataActArray = new ArrayList<ArrayList<Object>>();
dataActArray = getDataAct();

if(dataActArray.get(0).size() > 1) {

results = new ArrayList<Object>();
startSimulations = System.currentTimeMillis();

MCSimulation mcs = new MCSimulation(numRepetitions, dataActArray);
acyclic = mcs.topologicalSort();

if(acyclic == 1) {

System.out.println("Array (prima colonna) ordinato topologicamente:");
mcs.printSortArray();
mcs.makeInNodes();
System.out.println();
System.out.println("Matrice inNodes:");
mcs.printInNodes();
System.out.println();
System.out.println("Array inDegree:");
mcs.printInDegree();
System.out.println();
System.out.println();
System.out.println("Array outDegree:");
mcs.printOutDegree();
System.out.println();
System.out.println();
results = mcs.simulations();
stopSimulations = System.currentTimeMillis();

DecimalFormatSymbols symb = new DecimalFormatSymbols();
symb.setDecimalSeparator('.');
DecimalFormat df = new DecimalFormat();
df.setDecimalFormatSymbols(symb);
df.setMaximumFractionDigits(3);

timeSpentField.setText(""+((double)(stopSimulations-startSimulations)/1000));
maxTotalDurField.setText(""+df.format(results.get(0)));
sdField.setText(""+df.format(results.get(1)));
for(int i=0; i<activitiesNumber; i++) {
((JTextField)activitiesArray.get(i).get(5)).setText(""+((int[])results.get(2))[i]);
}

} else {

stopSimulations = System.currentTimeMillis();
JOptionPane.showMessageDialog(null,
"<html><body>The following activities make a cyclical project!!!<br>" +
"Please check all precedences in the activities!!!</body></html>", "Error",
JOptionPane.ERROR_MESSAGE);

}

} else {
System.out.println("Error: ci sono state delle eccezioni!");
}

}


pleaseWaitLabel.setVisible(false);

//Refresh MainPanel
repaint();
}
};

pleaseWaitLabel.setVisible(true);

//Refresh MainPanel
repaint();

algorithmThread.start();

}

banryu79
29-10-2008, 15:35
Bene, ora, fossi in te, rifattorizzerei quel "bloccone" di codice che viene eseguito nel thread esterno, se non altro per migliorare la leggibilità.
Ciao :)

@EDIT:
Ehm, c'è ancora qualcosa di 'sporco': fai processare al thread esterno del codice AWT (ovvero quando setti la JLabel a non visibile). Come detto sopra: L'EDT è l'unico thread valido da cui poter aggiornare lo stato visibile dell'interfaccia grafica utente. Questo implica, tra l'altro, che aggiornare la grafica da altri thread diversi dall'EDT può essere causa di comportamenti anomali e funzionamenti non corretti dell'interfaccia grafica.

Per ovviare dovresti far accodare l'operazione nella AWT Event Queue, come? Usando il metodo SwingUtilities.InvokeLater(Runnable) (swingutilities.invokelater example) a cui passi un thread (un Runnable) che puoi anche creare on the fly come hai fatto sopra, il cui scopo è appunto quello di invocare setVisible() sulla JLabel.

pumax84
30-10-2008, 09:34
Bene, ora, fossi in te, rifattorizzerei quel "bloccone" di codice che viene eseguito nel thread esterno, se non altro per migliorare la leggibilità.
Ciao :)
Senza dubbio è da sistemare un pochino! :D


@EDIT:
Ehm, c'è ancora qualcosa di 'sporco': fai processare al thread esterno del codice AWT (ovvero quando setti la JLabel a non visibile). Come detto sopra: L'EDT è l'unico thread valido da cui poter aggiornare lo stato visibile dell'interfaccia grafica utente. Questo implica, tra l'altro, che aggiornare la grafica da altri thread diversi dall'EDT può essere causa di comportamenti anomali e funzionamenti non corretti dell'interfaccia grafica.

Per ovviare dovresti far accodare l'operazione nella AWT Event Queue, come? Usando il metodo SwingUtilities.InvokeLater(Runnable) (swingutilities.invokelater example) a cui passi un thread (un Runnable) che puoi anche creare on the fly come hai fatto sopra, il cui scopo è appunto quello di invocare setVisible() sulla JLabel.
Mmm... sinceramente non sono molto esperto di Thread & co. Potresti farmi un semplice esempio? :)

Ah, dato che ci siamo... :D Dovrei utilizzare QUESTA (http://www.iro.umontreal.ca/~simardr/ssj/doc/html/umontreal/iro/lecuyer/randvar/TriangularGen.html) classe, in particolare il metodo nextDouble. Il problema è che tra i parametri c'è RandomStream (che è un'interfaccia)... Cosa dovrei passargli? :confused:

banryu79
30-10-2008, 09:51
Mmm... sinceramente non sono molto esperto di Thread & co. Potresti farmi un semplice esempio? :)

be, per esempio:

Runnable hideAndUpdate = new Runnable()
{
public void run()
{
pleaseWaitLabel.setVisible(false);
repaint();
}
};
SwingUtilities.invokeLater(hideAndUpdate);



Ah, dato che ci siamo... :D Dovrei utilizzare QUESTA (http://www.iro.umontreal.ca/~simardr/ssj/doc/html/umontreal/iro/lecuyer/randvar/TriangularGen.html) classe, in particolare il metodo nextDouble. Il problema è che tra i parametri c'è RandomStream (che è un'interfaccia)... Cosa dovrei passargli? :confused:
Non lo so di preciso non conosco quella libreria, però dovrai probabilmente passargli una delle classi che implementano quella interfaccia.
Vai a vedere quali classi implementano RandomStream; ancora meglio trova, se esistono, tutorial ed esempi di utilizzo di quella libreria (magari partendo dal sito dove l'hai trovata).

pumax84
30-10-2008, 10:00
be, per esempio:

Runnable hideAndUpdate = new Runnable()
{
public void run()
{
pleaseWaitLabel.setVisible(false);
repaint();
}
};
SwingUtilities.invokeLater(hideAndUpdate);

Devo richiamare SwingUtilities.invokeLater(hideAndUpdate) dopo l'esecuzione dell'altro thread (ovvero algorithmThread.start())?


Non lo so di preciso non conosco quella libreria, però dovrai probabilmente passargli una delle classi che implementano quella interfaccia.
Vai a vedere quali classi implementano RandomStream; ancora meglio trova, se esistono, tutorial ed esempi di utilizzo di quella libreria (magari partendo dal sito dove l'hai trovata).
Ah perfetto, quindi devo vedermi le classi che la implementano! Capito, grazie mille! :)

banryu79
30-10-2008, 11:29
Devo richiamare SwingUtilities.invokeLater(hideAndUpdate) dopo l'esecuzione dell'altro thread (ovvero algorithmThread.start())?

Devi richiamarlo da dentro quel thread.
Provo a schematizzare:

-> chiamata ActionListener [thread corrente: EDT]
|
---> esecuzione algoritmo [thread corrente: esterno]
|
---> nascondere JLabel [thread corrente: esterno]

Da cui la neccessità di accodare nell'Event Queue (perchè così verrà
eseguito dall'EDT) il codice che "nasconde" la label e richiede un repaint.

pumax84
30-10-2008, 11:59
Devi richiamarlo da dentro quel thread.

Ah ecco... Io avevo già provato in quel modo, ma la dichiarazione/inizializzazione del Runnable la facevo esterna al Thread, di conseguenza mi indicava che non riconosceva la variabile... :p Sto fuso io... :D

Ora l'ho messa all'interno e sembra andare. Incollo il codice per una conferma sulla correttezza! ;)

if(e.getSource() == computeButton) {

Thread algorithmThread = new Thread() {

@Override
public void run()
{
Runnable hideAndUpdate = new Runnable() {
public void run()
{
pleaseWaitLabel.setVisible(false);
repaint();
}
};

int numRepetitions = -1;
int acyclic = 0;

try {

if (getRepetitions() > 0)
numRepetitions = getRepetitions();
else
JOptionPane.showMessageDialog(null,
"<html><body>The format is not valid!<br>" +
"Please enter a positive integer<br>" +
"and less than 2147483648.</body></html>", "Error",
JOptionPane.ERROR_MESSAGE);
}

catch(NumberFormatException exception) {

JOptionPane.showMessageDialog(null,
"<html><body>The format is not valid!<br>" +
"Please enter a positive integer<br>" +
"and less than 2147483648.</body></html>", "Error",
JOptionPane.ERROR_MESSAGE);

}

if(numRepetitions > 0) {

ArrayList<ArrayList<Object>> dataActArray = new ArrayList<ArrayList<Object>>();
dataActArray = getDataAct();

if(dataActArray.get(0).size() > 1) {

results = new ArrayList<Object>();
startSimulations = System.currentTimeMillis();

MCSimulation mcs = new MCSimulation(numRepetitions, dataActArray);
acyclic = mcs.topologicalSort();

if(acyclic == 1) {

System.out.println("Array (prima colonna) ordinato topologicamente:");
mcs.printSortArray();
mcs.makeInNodes();
System.out.println();
System.out.println("Matrice inNodes:");
mcs.printInNodes();
System.out.println();
System.out.println("Array inDegree:");
mcs.printInDegree();
System.out.println();
System.out.println();
System.out.println("Array outDegree:");
mcs.printOutDegree();
System.out.println();
System.out.println();
results = mcs.simulations();
stopSimulations = System.currentTimeMillis();

DecimalFormatSymbols symb = new DecimalFormatSymbols();
symb.setDecimalSeparator('.');
DecimalFormat df = new DecimalFormat();
df.setDecimalFormatSymbols(symb);
df.setMaximumFractionDigits(3);

timeSpentField.setText(""+((double)(stopSimulations-startSimulations)/1000));
maxTotalDurField.setText(""+df.format(results.get(0)));
sdField.setText(""+df.format(results.get(1)));
for(int i=0; i<activitiesNumber; i++) {
((JTextField)activitiesArray.get(i).get(5)).setText(""+((int[])results.get(2))[i]);
}

} else {

stopSimulations = System.currentTimeMillis();
JOptionPane.showMessageDialog(null,
"<html><body>The following activities make a cyclical project!!!<br>" +
"Please check all precedences in the activities!!!</body></html>", "Error",
JOptionPane.ERROR_MESSAGE);

}

} else {
System.out.println("Error: ci sono state delle eccezioni!");
}

}

SwingUtilities.invokeLater(hideAndUpdate);
}
};

pleaseWaitLabel.setVisible(true);

//Refresh MainPanel
repaint();

algorithmThread.start();

}