Threads et Swing
Tache
longue dans un GUI
Source de GUIetTacheLongue1.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUIetTacheLongue1 extends JFrame {
JProgressBar barreProgression;
public GUIetTacheLongue1() {
super("GUIetTacheLongue1");
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
barreProgression = new JProgressBar();
JButton bouton = new JButton("Demarrer la tache longue");
contentPane.add(bouton,BorderLayout.NORTH);
contentPane.add(barreProgression,BorderLayout.SOUTH);
bouton.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
LongueTache1 tache = new LongueTache1();
tache.execute(barreProgression);
}
}
);
pack();
setVisible(true);
}
public static void main(String[] args) {
new GUIetTacheLongue1();
}
}
class LongueTache1 {
public void execute(JProgressBar barreProgression) {
barreProgression.setMinimum(0);
barreProgression.setMaximum(99);
barreProgression.setValue(0);
for(int i = 0; i < 100; i++ ) {
barreProgression.setValue(i);
System.out.print(".");
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
System.out.println("") ;
}
}
|
EXECUTION
$java GUIetTacheLongue1
.................................
|
...............................................
.................................
$
|
- Rmq : Arretez avec <ctrl-C> car
la fermeture de la fenetre et l'arret de l'appli ne sont pas
prévus pour alléger le code
- La "longue tache", visiblement,
fonctionne car elle se trace dans la console.
- Mais la barre de progression Swing n'est
mise à jour qu'à la fin !
|
- Problème :
Exécuter un tache relativement longue dans le cadre d'une
(GUI) interface graphique pour l'utilisateur
- la tache dure quelques secondes voire
des minutes
- L'interface doit pouvoir encore réagir
aux actions de l'utilisateur
- les lignes de la classe :
- La longue tache à exécuter
est simulée par des "sleep" dans sa méthode
execute()
- elle doit indiquer son avancement dans
une barre de progression
- mais aussi dans la console pour trace
!
|
multitache
===> thread
Source de GUIetTacheLongue2.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUIetTacheLongue2 extends JFrame {
JProgressBar barreProgression;
public GUIetTacheLongue2() {
super("GUIetTacheLongue2");
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
barreProgression = new JProgressBar();
JButton bouton = new JButton("Demarrer la tache longue");
contentPane.add(bouton,BorderLayout.NORTH);
contentPane.add(barreProgression,BorderLayout.SOUTH);
bouton.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
Thread t = new Thread() {
public void run() {
LongueTache2 tache = new LongueTache2();
tache.execute(barreProgression);
}
};
t.start();
}
}
);
pack();
setVisible(true);
}
public static void main(String[] args) {
new GUIetTacheLongue2();
}
}
class LongueTache2 {
public void execute(JProgressBar barreProgression) {
barreProgression.setMinimum(0);
barreProgression.setMaximum(99);
barreProgression.setValue(0);
for(int i = 0; i < 100; i++ ) {
barreProgression.setValue(i);
System.out.print(".");
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
System.out.println("") ;
}
}
|
EXECUTION
$java GUIetTacheLongue2
.................................
|
...............................................
.................................
$
|
- Le thread "longue tache", visiblement,
fonctionne car il se trace dans la console.
- Et la barre de progression Swing est mise
à jour progressivement
|
- les lignes de la classe :
- Pour que la longue tache et le Gui soient
exécutés "en même temps", on lance
un thread de plus pour la longue tache
|
thread
Swing (et AWT)
Source de ThreadSwing.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class ThreadSwing {
public static void main(String args[]) {
System.out.println("main -----> thread name = "
+ Thread.currentThread().getName());
JFrame frame = new JFrame("ThreadSwing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton bouton = new JButton("Afficher");
frame.getContentPane().add(bouton);
bouton.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("actionPerformed -----> thread name = "
+ Thread.currentThread().getName());
infoAllThread();
}
});
frame.pack();
frame.setVisible(true);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {}
}
public static void infoThread(Thread thread, String indent) {
if (thread == null)
System.out.println(indent + "Thread null !!");
else
System.out.println(indent + "Thread : name=" + thread.getName() +
" priority=" + thread.getPriority() +
" daemon=" + thread.isDaemon() +
" alive=" + thread.isAlive());
}
public static void infoThreadGroup(ThreadGroup tGroup) {
infoThreadGroup(tGroup, "");
}
public static void infoThreadGroup(ThreadGroup tGroup, String indent) {
if (tGroup == null)
System.out.println(indent + "GroupThread null !");
else {
System.out.println(indent + "ThreadGroup" +
" name:" + tGroup.getName() +
" priority_max=" + tGroup.getMaxPriority() +
" daemon=" + tGroup.isDaemon());
int nbreThreadFils = tGroup.activeCount();
System.out.println(indent + "nbreThreadFils="+nbreThreadFils);
Thread[] threadFils = new Thread[nbreThreadFils];
tGroup.enumerate(threadFils,false);
for (int i = 0; i < nbreThreadFils; i++)
infoThread(threadFils[i], indent + " ");
int nbreGroupFils = tGroup.activeGroupCount();
System.out.println(indent + "nbreGroupFils="+nbreGroupFils);
ThreadGroup[] tGroupFils = new ThreadGroup[nbreGroupFils];
tGroup.enumerate(tGroupFils,false);
for (int i = 0; i < nbreGroupFils; i++)
infoThreadGroup(tGroupFils[i],indent + " ");
}
}
public static ThreadGroup getThreadGroupRoot() {
ThreadGroup current, parent;
current = Thread.currentThread().getThreadGroup();
while ((parent = current.getParent()) != null)
current = parent;
return current;
}
public static void infoAllThread() {
ThreadGroup threadGroupRoot = getThreadGroupRoot();
if (threadGroupRoot == null)
System.out.println("ThreadGroupRoot null!");
else
infoThreadGroup(threadGroupRoot);
}
|
EXECUTION
main -----> thread name = main
actionPerformed -----> thread name = AWT-EventQueue-0
ThreadGroup name:system priority_max=10 daemon=false
nbreThreadFils=10
Thread : name=Reference Handler priority=10 daemon=true alive=true
Thread : name=Finalizer priority=8 daemon=true alive=true
Thread : name=Signal Dispatcher priority=10 daemon=true alive=true
Thread : name=CompilerThread0 priority=10 daemon=true alive=true
Thread null !!
Thread null !!
Thread null !!
Thread null !!
Thread null !!
Thread null !!
nbreGroupFils=1
ThreadGroup name:main priority_max=10 daemon=false
nbreThreadFils=5
Thread : name=main priority=5 daemon=false alive=true
Thread : name=AWT-Windows priority=6 daemon=true alive=true
Thread : name=AWT-Shutdown priority=5 daemon=false alive=true
Thread : name=Java2D Disposer priority=10 daemon=true alive=true
Thread : name=AWT-EventQueue-0 priority=6 daemon=false alive=true
nbreGroupFils=0
|
|
- les lignes de la classe :
- cette classe fournit un excellent utilitaire
pour tracer les threads
- 2 threads sont directement programmés
:
- mainAWT-EventQueue
- AWT-EventQueue qui traite l'actionPerformed
- Swing (et AWT) dispose d'un thread spécifique
pour traiter les événements :
- l'event dispatcher : AWT-EventQueue
- il exécute les "handlers"/méthodes
associées aux événements
- une file permet de mettre en attente ordonnée
les événements qui arrivent
- les events paint sont traités sur
ce même thread
- simplement un PaintManager permet de regrouper
les events paint d'un même composant
|
Deadlock
d'initialisation de GUI
Source de SwingThreadDeadlock.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class SwingThreadDeadlock extends JPanel
implements ComponentListener
{
static JFrame frame;
JTextArea trace;
String nl = "\n";
public SwingThreadDeadlock() {
super(new BorderLayout());
trace = new JTextArea();
trace.setEditable(false);
JScrollPane scrollPane = new JScrollPane(trace);
scrollPane.setPreferredSize(new Dimension(350, 200));
JPanel panel = new JPanel(new BorderLayout());
JLabel label = new JLabel("label inutile 1", JLabel.CENTER);
label.addComponentListener(this);
panel.add(label, BorderLayout.WEST);
label = new JLabel("label inutile 2", JLabel.CENTER);
label.addComponentListener(this);
panel.add(label, BorderLayout.EAST);
panel.addComponentListener(this);
add(scrollPane, BorderLayout.CENTER);
add(panel, BorderLayout.PAGE_END);
addComponentListener(this);
}
protected void tracer(String message) {
trace.append(message + nl);
trace.setCaretPosition(trace.getDocument().getLength());
}
public void componentHidden(ComponentEvent e) {
String message = "evenement componentHidden";
System.out.println(message);
tracer(message + "provenant de "
+ e.getComponent().getClass().getName());
}
public void componentMoved(ComponentEvent e) {
Component c = e.getComponent();
String message = "evenement componentMoved";
System.out.println(message);
tracer(message + " provenant de "
+ c.getClass().getName()
+ " nouvelle position : "
+ c.getLocation().x
+ ", "
+ c.getLocation().y);
}
public void componentResized(ComponentEvent e) {
Component c = e.getComponent();
String message = "evenement componentResized";
System.out.println(message);
tracer(message + " provenant de "
+ c.getClass().getName()
+ " nouvelle taille: "
+ c.getSize().width
+ ", "
+ c.getSize().height);
}
public void componentShown(ComponentEvent e) {
String message = "evenement componentShown";
System.out.println(message);
tracer(message + " provenant de "
+ e.getComponent().getClass().getName());
}
private static void createAndShowGUI() {
JFrame.setDefaultLookAndFeelDecorated(true);
frame = new JFrame("SwingThreadDeadlock");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JComponent newContentPane = new SwingThreadDeadlock();
newContentPane.setOpaque(true);
frame.setContentPane(newContentPane);
System.out.println("pret a pack-er");
frame.pack();
frame.setVisible(true);
System.out.println("pack et show FAIT !");
}
public static void main(String[] args) {
createAndShowGUI();
}
}
|
EXECUTION
quelques fois se produit un blocage :
pret a pack-er
evenement componentResized
<ctrl-C>
|
|
- les lignes de la classe :
- le problème vient du fait que :
- pack provoque des events componentResized
- l'handler de traitement appel la méthode
tracer
- la méthode tracer agit sur le composant
JTextAera
- le composant JTextAera n'est pas encore
visible
- ce code s'éxécute sur 2
threads :
- main pour pack, setVisible, ...
- l'event dispatcher pour les handlers
- d'où l'interblocage (deadlock)
|
- une "rustine
Source de
SwingThreadDeadlockMieux.java
...
protected void tracer(String message) {
if (trace.isShowing()) {
trace.append(message + nl);
trace.setCaretPosition(trace.getDocument().getLength());
}
}
...
|
EXECUTION
pret a pack-er
evenement componentResized
evenement componentMoved
evenement componentResized
evenement componentResized
evenement componentMoved
evenement componentResized
evenement componentResized
evenement componentMoved
evenement componentResized
evenement componentResized
pack et show FAIT !
evenement componentResized
evenement componentResized
evenement componentMoved
evenement componentMoved
|
|
- les lignes de la classe :
- Il n'y a pas d'affichage dans la fenetre
jusqu'au message "pack et show fait"
- Ceci résoud l'interblocage entre
les threads sur un point du code, mais il reste peut-être
d'autres sources d'interblocage
|
Pattern
de création d'un GUI
Source de SwingThreadOK.java
....
public static void main(String[] args) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
|
EXECUTION
pret a pack-er
pack et show FAIT !
evenement componentResized
evenement componentResized
evenement componentResized
evenement componentMoved
evenement componentResized
evenement componentResized
evenement componentMoved
evenement componentResized
evenement componentResized
evenement componentResized
evenement componentMoved
evenement componentShown
|
|
- les lignes de la classe :
- invokeLater( ...) fera exécuter
la méthode createAndshowGUI dans le thread event-dispatcher
- cette demande est ajoutéedans la
file de événements en attente de traitement
- comme il n'y a pas encore de composant,
ce sera le premier traitement
- ceci évite le deadlock
- Précédemment, le probléme
venait du fait que :
- des composants étaient réalisés
avant que le JTextAera le soit !
- un composant est réalisé
(realized) si :
- il est contenu dans un conteneur réalisé
- pour une fenetre top-level, par les méthodes
:
- setVisible(true)
- show()
- pack()
- un composant réalisé est
apte à être "paint()"
- invokeLater(Runnable tache) est une méthode static
de la classe SwingUtilities :
- elle ajoute la tache à "runner"
dans la file des événements à traitre par
le thread event-dispatcher
- cette méthode retourne immédiatement
dans le thread appelant
|
Tache
périodique
Source de GUIetTachePeriodique8.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUIetTachePeriodique8 extends JFrame
implements ActionListener {
JLabel etatTachePeriodique;
JButton boutonGo;
int compteur;
javax.swing.Timer timer;
public GUIetTachePeriodique8() {
super("GUIetTachePeriodique8");
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
boutonGo = new JButton("Demarrer");
contentPane.add(boutonGo,BorderLayout.NORTH);
etatTachePeriodique = new JLabel("pas de tache periodique");
contentPane.add(etatTachePeriodique,BorderLayout.CENTER);
boutonGo.addActionListener(this);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand() != null) {
boutonGo.setEnabled(false);
compteur = 6;
etatTachePeriodique.setText("reste "+ compteur +" secondes");
timer = new Timer(1000, this);
timer.setInitialDelay(0);
timer.setCoalesce(true);
timer.start();
}
else { // l'event de timer n'a pas de nom de commande !
if (--compteur <= -1) {
timer.stop();
boutonGo.setEnabled(true);
etatTachePeriodique.setText("fini !");
} else
etatTachePeriodique.setText("reste "+compteur+" secondes");
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
new GUIetTachePeriodique8();
}
});
}
}
|
EXECUTION
|
- les lignes de la classe :
- la tache à effectuer a changé
:
on déclanche un compte à rebours de 6 secondes
- toutes les secondes, il faut changer le
label affiché et, à la fin, afficher fini.
- la tache périodique est éxécutée
par un Timer Swing, ce qui assure l'exécution dans le
thread event-dispatcher
|
- javax.swing.Timer
- le constructeur Timer( period, listener)
instancie un Timer de la période désirée
avec un listener désigné
- aux instants ad-hoc, le timer enverra
un événement ActionEvent au listener
- cet event n'a pas d'attribut actionCommand
- la tache à effectuer périodiquement
devra l'être à partir du handler actionPerformed
- donc, a priori, dans le thread event-dispatcher
- si une tache longue est à déclancher,
il faudra l'exécuter dans un thread qui sera démarré
(start) à partir du corps de l'actionPerformed
- la méthode setInitialDelay(délai)
définit un délai initial avant l'établissement
des périodes
- la méthode setCoalesce(booléen)
définit la politique du timer en cas d'engorgement : si
true, et s'il y a plusieurs events à générer
(du fait d'un retard), alors le timer n'envoie qu'un unique event.
- la méthode start() démarre
le timer
- la méthode stop() arrête
le timer
Règle
d'Unicité du thread "GUI"
Source de GUIetTacheLongue3.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUIetTacheLongue3 extends JFrame implements ActionListener {
JProgressBar barreProgression;
JButton bouton;
public GUIetTacheLongue3() {
super("GUIetTacheLongue3");
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
barreProgression = new JProgressBar();
barreProgression.setMinimum(0);
barreProgression.setMaximum(99);
barreProgression.setValue(0);
bouton = new JButton("Demarrer la tache longue");
contentPane.add(bouton,BorderLayout.NORTH);
contentPane.add(barreProgression,BorderLayout.SOUTH);
bouton.addActionListener(this);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
bouton.setEnabled(false);
Thread t = new Thread() {
public void run() {
LongueTache3 tache = new LongueTache3();
tache.execute();
}
};
t.start();
}
class LongueTache3 {
public void execute() {
for(int i = 0; i < 100; i++ ) {
setProgression(i);
System.out.print(".");
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
System.out.println("") ;
reset();
}
}
void setProgression(final int niveau) {
Runnable mettreAJourProgression = new Runnable() {
public void run() {
barreProgression.setValue(niveau);
}
};
SwingUtilities.invokeLater(mettreAJourProgression);
}
void reset() {
Runnable remettreAZero = new Runnable() {
public void run() {
barreProgression.setValue(0);
bouton.setEnabled(true);
}
};
SwingUtilities.invokeLater(remettreAZero);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
new GUIetTacheLongue3();
}
});
}
}
|
- les lignes de la classe :
- Dans cet exemple, il y a 3 threads :
- main
- event-dispatcher
- longue tache
- les actions sur composants Swings sont
tous dans le thread event grace à l'appel à la
méthode invokeLater()
- Le Pattern d'unicité du thread
"GUI" :
- Tout le code qui affecte des composants
Swing ou qui dépend de leurs états doit être
exécuter par le thread event-dispatcher.
- A l'exception de :
- quelques méthodes "thread-safe"
de JComponent, mises dans la file d'attente de l'event-dispatcher
:
- repaint()
- revalidate()
- invalidate()
- les listes de Listener peuvent être
modifiées de n'importe quel thread
- donc les méthodes addListenerTypeListener()
et removeListenerTypeListener()
- tant qu'aucun composant n'est réalisé,
il est possible d'initialiser les composants du GUI dans le thread
main :
....
public static void main(String[] args) {
create_GUI_sans_aucun_pack_show_setVisible_true();
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
Show_pack_GUI();
autres_traitements_GUI();
}
});
}
}
|
SwingWorker
: la tache longue .... proprement
Source de GUIetTacheLongue4.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUIetTacheLongue4 extends JFrame
implements ActionListener {
JLabel etatLongueTache;
JButton bouton;
SwingWorker worker4;
public GUIetTacheLongue4() {
super("GUIetTacheLongue4");
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
bouton = new JButton("Demarrer la tache longue");
contentPane.add(bouton,BorderLayout.NORTH);
etatLongueTache = new JLabel("pas de Longue tache");
contentPane.add(etatLongueTache,BorderLayout.SOUTH);
bouton.addActionListener(this);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
bouton.setEnabled(false);
etatLongueTache.setText("tache en cours");
worker4 = new LongueTache4();
worker4.start();
}
class LongueTache4 extends SwingWorker {
private int fin;
public LongueTache4() {
super();
fin = (int)(Math.random()*100)+100;
}
public Object construct() {
for(int i = 0; i < fin; i++ ) {
System.out.print(".");
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
System.out.println("") ;
return new Integer(fin);
}
public void finished() {
bouton.setEnabled(true);
// fin = ..... get(); possible aussi
etatLongueTache.setText("tache finie = " + fin);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
new GUIetTacheLongue4();
}
});
}
}
|
- les lignes de la classe :
- Dans cet exemple, la tache n'affiche plus
sa progression.
- la tache est désormais une sous-classede
SwingWorker au lieu de Thread
- son fonctionnement anciennement dans la
méthode run() de Thread se retrouve dans la méthode
construct() de SwingWorker
- elle est démarréepar start()
- en plus dans finished(), se trouve le
code à exécuter quand construct est terminé
: affichage du résultat de construct et re-activation
du bouton stop
- SwingWorker
est une classe non inclue dans les APIs de la distribution J2SE
- elle permet d'implémenter facilement
une tache de fond dans un GUI
- Source de SwingWorker.java
- il faut tout d'abord hériter de
cette classe :
- re-définir la méthode construct()
qui est l'équivalente de la méthode run(),
- mais retourne un Object
- sert typiquement à implémenter
:
- de long calculs
- une attente bloquante sur entrée/sortie
disque ou sur le net
- une attente de ressource
- éventuellement re-définir
la méthode finished() qui est appelée quand
"construct" est terminée
- elle s'exécute sur le thread event-dispatcher
!
- peut servir à implémenter
les effets sur le GUI ..
- la méthode get() retourne
l'objet résultat de construct et attend si construct n'est
pas fini !
- la méthode start() permet
de lancer l'exécution de contruct() dans un nouveau thread
Swing
Worker : la tache longue avec interruption possible
Source de GUIetTacheLongue5.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUIetTacheLongue5 extends JFrame
implements ActionListener {
JLabel etatLongueTache;
JButton boutonGo;
SwingWorker worker5;
JButton boutonStop;
public GUIetTacheLongue5() {
super("GUIetTacheLongue5");
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
boutonGo = new JButton("Demarrer la tache longue");
contentPane.add(boutonGo,BorderLayout.NORTH);
boutonStop = new JButton("Stopper");
boutonStop.setEnabled(false);
boutonStop. setBackground(Color.RED);
contentPane.add(boutonStop,BorderLayout.SOUTH);
etatLongueTache = new JLabel("pas de Longue tache");
contentPane.add(etatLongueTache,BorderLayout.CENTER);
boutonGo.addActionListener(this);
boutonStop.addActionListener(this);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("Demarrer la tache longue")) {
boutonGo.setEnabled(false);
boutonStop.setEnabled(true);
etatLongueTache.setText("tache en cours");
worker5 = new LongueTache5();
worker5.start();
}
else {
boutonStop.setEnabled(false);
worker5.interrupt();
boutonGo.setEnabled(true);
}
}
class LongueTache5 extends SwingWorker {
private int fin;
public LongueTache5() {
super();
fin = (int)(Math.random()*100)+100;
}
public Object construct() {
try {
for(int i = 0; i < fin; i++ ) {
System.out.print(".");
if (Thread.interrupted())
throw new InterruptedException();
Thread.sleep(100);
}
} catch (InterruptedException e) {
System.out.println("interrupt !");
return new String("interrupt !");
}
System.out.println("") ;
return String.valueOf(fin);
}
public void finished() {
boutonGo.setEnabled(true);
boutonStop.setEnabled(false);
String valeurFin = (String)get(); // possible aussi
etatLongueTache.setText("tache finie = " + valeurFin);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
new GUIetTacheLongue5();
}
});
}
}
|
EXECUTION
|
- les lignes de la classe :
- Désormais, un bouton permet d'interrompre
(définitivement) la tache longue
- le bouton "stop" appelle la
méthode interrupt de SwingWorker
- la méthode interrupt() de SwingWorker
fonctionne en partie automatiquement (comme interrupt de Thread)
et en partie selon la technique de "stop propre" d'un
thread
- permet d'arreter des méthodes comme
sleep() et wait() de Thread pendant l'exécution de construct
- ce qui "throw" une InterruptedException
- sinon en testant Thread.interrupted()
dans le corps de la méthode construct
- le programmeur devra s'arrurer que cela
ne met pas son application dans un état incohérent
|
SwingWorker
en interaction avec l'event-dispatcher
Source de GUIetTacheLongue6.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUIetTacheLongue6 extends JFrame
implements ActionListener {
JProgressBar barreProgression;
JButton boutonGo;
SwingWorker worker6;
JButton boutonStop;
public GUIetTacheLongue6() {
super("GUIetTacheLongue6");
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
boutonGo = new JButton("Demarrer la tache longue");
contentPane.add(boutonGo,BorderLayout.NORTH);
boutonStop = new JButton("Stopper");
boutonStop.setEnabled(false);
boutonStop. setBackground(Color.RED);
contentPane.add(boutonStop,BorderLayout.SOUTH);
barreProgression = new JProgressBar();
barreProgression.setMinimum(0);
barreProgression.setMaximum(99);
barreProgression.setValue(0);
contentPane.add(barreProgression,BorderLayout.CENTER);
boutonGo.addActionListener(this);
boutonStop.addActionListener(this);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("Demarrer la tache longue")) {
boutonGo.setEnabled(false);
boutonStop.setEnabled(true);
barreProgression.setValue(0);
worker6 = new LongueTache6();
worker6.start();
}
else {
boutonStop.setEnabled(false);
worker6.interrupt();
boutonGo.setEnabled(true);
}
}
void setProgression(final int niveau) {
Runnable mettreAJourProgression = new Runnable() {
public void run() {
barreProgression.setValue(niveau);
}
};
SwingUtilities.invokeLater(mettreAJourProgression);
}
class LongueTache6 extends SwingWorker {
public Object construct() {
try {
for(int i = 0; i < 100; i++ ) {
System.out.print(".");
setProgression(i);
if (Thread.interrupted())
throw new InterruptedException();
Thread.sleep(100);
}
} catch (InterruptedException e) {
System.out.println("interrupt !");
return new String("interrupt !");
}
System.out.println("") ;
return new String("tache accomplie");
}
public void finished() {
boutonGo.setEnabled(true);
boutonStop.setEnabled(false);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
new GUIetTacheLongue6();
}
});
}
}
|
- les lignes de la classe :
- Nous ré-introduisons la barre de
progression
- donc le thread SwingWorker a besoin de
la mettre à jour
- Celle-ci doit être manipulée
dans le cadre du thread event-dispatcher
- d'où l'emploi d'invokeLater
Bloquer
le SwingWorker et le GUI
Source de GUIetTacheLongue7.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GUIetTacheLongue7 extends JFrame
implements ActionListener {
JLabel etatLongueTache;
JButton boutonGo;
SwingWorker worker7;
JButton boutonStop;
public GUIetTacheLongue7() {
super("GUIetTacheLongue7");
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
boutonGo = new JButton("Demarrer la tache longue");
contentPane.add(boutonGo,BorderLayout.NORTH);
boutonStop = new JButton("Stopper");
boutonStop.setEnabled(false);
boutonStop. setBackground(Color.RED);
contentPane.add(boutonStop,BorderLayout.SOUTH);
etatLongueTache = new JLabel("pas de Longue tache");
contentPane.add(etatLongueTache,BorderLayout.SOUTH);
boutonGo.addActionListener(this);
boutonStop.addActionListener(this);
pack();
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("Demarrer la tache longue")) {
boutonGo.setEnabled(false);
boutonStop.setEnabled(true);
etatLongueTache.setText("tache en cours");
worker7 = new LongueTache7();
worker7.start();
}
else {
boutonStop.setEnabled(false);
worker7.interrupt();
boutonGo.setEnabled(true);
}
}
class LongueTache7 extends SwingWorker {
private int fin;
public LongueTache7() {
super();
fin = (int)(Math.random()*100)+100;
}
public Object construct() {
try {
for(int i = 0; i < fin; i++ ) {
System.out.print(".");
if (i == fin/2)
if (!confirmation())
return new String("Arrete a la moitie !");
if (Thread.interrupted())
throw new InterruptedException();
Thread.sleep(100);
}
} catch (InterruptedException e) {
System.out.println("interrupt !");
return new String("interrupt !");
}
System.out.println("") ;
return String.valueOf(fin);
}
public void finished() {
boutonGo.setEnabled(true);
boutonStop.setEnabled(false);
String valeurFin = (String)get();
etatLongueTache.setText("tache finie = " + valeurFin);
}
}
boolean confirmation() throws InterruptedException {
class Dialoguer implements Runnable {
public boolean confirme;
public void run() {
Object[] options = { "OK", "CANCEL" };
if ( JOptionPane.showOptionDialog(null,
"Click OK to continue", "Tache : Encore la moitie !",
JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,
null, options, options[0])
== JOptionPane.YES_OPTION)
confirme = true;
else
confirme = false;
}
}
Dialoguer dialogue = new Dialoguer();
try {
SwingUtilities.invokeAndWait(dialogue);
}
catch (java.lang.reflect.InvocationTargetException e) {
e.printStackTrace();
}
return dialogue.confirme;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
new GUIetTacheLongue7();
}
});
}
}
|
EXECUTION
|
- les lignes de la classe :
- A la moitié de l'accomplissement
de la tache longue, la tache est bloquée et un popup de
dialogue oblige l'utilisateur à répondre à
la question de continuer ou non la tache.
- c'est délicat à programmer
car le composant dialogue doit s'afficher dans le thread event-dispatcheur,
tandis que le thread Worker doit être bloqué en
attente de réponse au dialogue.
- la méthode invokeLater retournant
immédiatement dans l'appelant ne peut convenir
- la méthode invokeAndWait() fonctionne
comme invokeLater mais attend l'exécution avant de revenir
!
- la méthode invokeAndWait(Runnable
tache) est une méthode static de la classe SwingUtilities
:
- elle ajoute la tache à "runner"
dans la file des événements à traitre par
le thread event-dispatcher
- cette méthode attend, dans le thread
appelant, que la tache soit exécuté !
- donc
il faut s'assurer que la tache ne risque pas de provoquer un
interblocage (manipule pas de verrou, ...)
- Un exemple d'utilisation : le dialogue
"modal", boite de dialogue bloquante
void afficheMaintenantDialog()
{
SwingUtilities.invokeAndWait(
new Runnable() {
public void run() {
JOptionPane.showMessageDialog
(maFrameTopLevel, "Coucou");
}
});
}
|
|
exercice
- 2 perroquets et un JTextArea.
- Chaque perroquet répètte
au début son cri de 1 à 20 fois avec une
attente aléatoire
en l'ajoutant dans le JTextArea
- Rectifiez le code des classes ci-dessous
qui est fortement susceptible d'interbloquer !
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class SwingEtThread extends JFrame
{
public SwingEtThread() {
super("Swing et Thread");
JTextArea traceur;
this.setDefaultLookAndFeelDecorated(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
traceur = new JTextArea();
traceur.setEditable(false);
JScrollPane scrollPane = new JScrollPane(traceur);
scrollPane.setPreferredSize(new Dimension(350, 200));
this.getContentPane().add(scrollPane);
this.pack();
this.setVisible(true);
Perroquet perroquet1 = new Perroquet("coco", traceur);
perroquet1.start();
Perroquet perroquet2 = new Perroquet("jaco", traceur);
perroquet2.start();
}
public static void main(String[] args) {
JFrame frame = new SwingEtThread();
}
}
class Perroquet extends Thread {
private String cri;
private JTextArea traceur;
public Perroquet(String c, JTextArea t) {
super();
cri = c;
traceur = t;
}
public void run() {
int fois = (int)(Math.random()*20);
for (int i=0; i<fois; i++) {
try {
Thread.sleep((long)(Math.random()*500));
} catch(InterruptedException e) { }
traceur.append(cri + "\n");
traceur.setCaretPosition(traceur.getDocument().getLength());
}
}
}
|
- correction