Transcript
Page 1: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Badr Benmammar

[email protected]

Thread et Swing

Page 2: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Plan

Tâche longue dans un une interface utilisateur graphique

(GUI )

Deadlock d’initialisation de GUI

Pattern de création d’un GUI

Tâche périodique dans un GUI

Règle d’unicité du thread GUI

Class SwingWorker : tâche longue proprement

Tâche longue avec interruption possible

Interaction avec l’event-dispatcher

Page 3: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Tâche longue dans un GUI

Problème : exécuter une tâche relativement longue dans le

cadre d’une (GUI) interface graphique pour l’utilisateur.

La tâche dure quelques secondes voire des minutes.

L’interface doit pouvoir encore réagir aux actions de

l’utilisateur.

La longue tâche à 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.

Page 4: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Tâche longue dans un GUIimport java.awt.*;

import java.awt.event.*;

import javax.swing.*;

public class GUIetTacheLongue1 extends JFrame {

JProgressBar barreProgression;

public GUIetTacheLongue1() {

super("GUIetTacheLongue1");

Container contentPane = getContentPane();

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("") ; }}

La tâche longue

Classe interne anonyme pour installer un écouteur correspond au

composant bouton

Page 5: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Tâche longue dans un GUI

.................................

...............................................

.................................$

Remarque : Arrêtez avec <ctrl-C> car la fermeture de la fenêtre et l’arrêt de l’appli ne sont pas prévus pour alléger le code.La "longue tâche", visiblement, fonctionne car elle se trace dans la console. Mais la barre de progression Swing n’est mise à jour qu’à la fin !

Exécution:

Page 6: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Multitâche avec un threadimport java.awt.*;

import java.awt.event.*;

import javax.swing.*;

public class GUIetTacheLongue2 extends JFrame {

JProgressBar barreProgression;

public GUIetTacheLongue2() {

super("GUIetTacheLongue2");

Container contentPane = getContentPane();

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);} //fin constructeur

public static void main(String[] args) { new GUIetTacheLongue2(); }} // fin de la classe 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("") ; }}

Une nouvelle classe interne anonyme qui est imbriquée dans la précédente classe interne

anonyme afin d’implémenter le thread.

Page 7: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Multitâche avec un thread Pour que la longue tâche et le Gui soient exécutés "en même temps", on lance un

thread de plus pour la longue tâche.

Exécution :.................................

...............................................

.................................$

Le thread "longue tâche", visiblement, fonctionne car il se trace dans la console

et la barre de progression Swing est mise à jour progressivement.

Page 8: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Utilitaire pour tracer les threads

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");

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());} }); frame.pack(); frame.setVisible(true);}}

Exécution :main -----> thread name = mainactionPerformed -----> thread name = AWT-EventQueue-0

Page 9: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Utilitaire pour tracer les threads Ce code s’exécute sur 2 threads :

main pour pack, setVisible (réaliser les composants). AWT-EventQueue-0 pour l’action (les événements).

Swing (et AWT) dispose d’un thread spécifique pour traiter les événements : l’event dispatcher : AWT-EventQueue-0 qui 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.

Page 10: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Deadlock d’initialisation de GUIimport ….

public class SwingThreadDeadlock extends JPanel implement ComponentListener{

private static JFrame frame;

private JTextArea trace;

public SwingThreadDeadlock() {

….

} void componentHidden(ComponentEvent e) {} // invoquer quand le composant devient

invisibleVoid componentMoved(ComponentEvent e) {} // invoquer quand le composant change de

positionVoid componentResized(ComponentEvent e) {} // invoquer quand le composant change de

taille void componentShown(ComponentEvent e) {} // invoquer quand le composant devient

visible

private static void createAndShowGUI() {… frame.pack();frame.setVisible(true);…}public static void main(String[] args) {createAndShowGUI();}}

C’est un écouteur de composant

Page 11: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Deadlock d’initialisation de GUI

private static void createAndShowGUI() {… frame.pack();frame.setVisible(true);…}public static void main(String[] args) {createAndShowGUI();}}

public void componentResized (ComponentEvent e) {

Afficher un texte dans un composant qui appartient a la frame. (trace de type JTextAera).}

pack provoque des events componentResized.

La méthode componentResized agit sur le composant JTextAera

qui n’est pas encore visible.

D’où l’interblocage (deadlock).

Solution : il faut s’assurer que tous les composants sont

visibles avant de lancer les évènements.

AWT-EventQueue-0

Page 12: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Pattern de création d’un GUI

public static void main(String[] args) { SwingUtilities.invokeLater (new Runnable() { public void run() { createAndShowGUI(); } }); }

invokeLater (Runnable tache) est une méthode static de la classe SwingUtilities : Fera exécuter la méthode createAndshowGUI dans le thread

event-dispatcher (AWT-EventQueue-0). Cette demande est ajoutée dans la file des événements en

attente de traitement, comme il n’y a pas encore de composant, ce sera le premier traitement et ceci évite le deadlock.

Cette méthode retourne immédiatement dans le thread appelant.

Page 13: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Tâche périodique dans un GUI

Tâche à effectuer : déclencher un compte à rebours de 6 secondes.

Toutes les secondes, il faut changer le label affiché et, à la fin, afficher fini.

La tâche périodique est exécutée par un Timer Swing. Le Timer Swing se trouve dans javax.swing. Différent de Timer qui est dans java.util.

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.

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’encombrement : 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.

Page 14: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

public class GUIetTachePeriodique8 extends JFrame implements ActionListener {

private JLabel etatTachePeriodique;

private JButton boutonGo;

private int compteur;

private Timer timer;

public GUIetTachePeriodique8() {

super("GUIetTachePeriodique8");

Container contentPane = getContentPane();

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);

}

Tâche périodique dans un GUIpublic void actionPerformed(ActionEvent e) {if (e.getActionCommand() != null) {boutonGo.setEnabled(false);compteur = 6;etatTachePeriodique.setText("reste "+ compteur +" secondes");timer = new Timer(1000, this); /* chaque seconde, le timer envoie un événement à son écouteur */timer.setInitialDelay(1000); // délai initialtimer.setCoalesce(true); /* true, s’il y a plusieurs events à générer (encombrement), alors le timer n’envoie qu’un unique event*/

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();}});}}

Page 15: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Règle d’unicité du thread GUI

Le modèle 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:

repaint() : pour forcer le rafraîchissement de l’affichage

d’un composant.

revalidate() et invalidate() : pour forcer un composant à

réorganiser ses enfants en fonction du layout choisi. invokeLater est une méthode "thread-safe".

Page 16: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Règle d’unicité du thread GUIimport 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(); 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(); }

AWT-EventQueue-0

Thread-3

Le constructeur

Page 17: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Règle d’unicité du thread GUIclass 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); }

AWT-EventQueue-0

public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { new GUIetTacheLongue3(); } }); }}

Exécution:

Page 18: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

Règle d’unicité du thread GUI

Dans cet exemple, il y a 3 threads :

main : réaliser les composants.

AWT-EventQueue-0 : traiter les évènements.

Thread-3 : réaliser la tâche longue.

Les actions sur composants Swings sont tous dans le thread

event dispatcher grâce à l’appel à la méthode invokeLater().

Page 19: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

SwingWorker : tâche longue proprement

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(); 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() { 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); etatLongueTache.setText("tache finie = " + fin); }} public static void main(String[] args) { SwingUtilities.invokeLater( new Runnable() { public void run() {new GUIetTacheLongue4();} }); }} // fin de la classe GUIetTacheLongue4

Page 20: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

SwingWorker : tâche longue proprement

Dans cet exemple, la tâche n’affiche plus sa progression.

La tâche est désormais une sous-classe de SwingWorker au lieu de

Thread.

SwingWorker permet d’implémenter facilement une tâche de fond

dans un GUI.

Son fonctionnement anciennement dans la méthode run() de Thread

se retrouve dans la méthode construct() de SwingWorker.

Elle est démarrée par start(), en plus dans finished(), se trouve le

code à exécuter quand construct est terminé.

Affichage du résultat de construct et réactivation du bouton stop.

Page 21: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

SwingWorker : tâche longue proprement

Il faut tout d’abord hériter de cette classe : Redé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.

Eventuellement redéfinir la méthode finished() qui est appelée quand "construct" est terminée. Elle s’exécute sur le thread event-dispatcher et peut servir à implémenter les effets sur le GUI.

La méthode start() permet de lancer l’exécution de contruct() dans un nouveau thread.

Exécution:

Page 22: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

SwingWorker : tâche longue avec interruption possible

Désormais, un bouton permet d’interrompre (définitivement) la tâche longue.

Exécution normale :

Exécution avec interruption :

Page 23: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

SwingWorker : tâche longue avec interruption possible

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();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.CENT

ER);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() { fin = (int)(Math.random()*100)+100; }

public Object construct() { try { for(int i = 0; i < fin; i++ ) { System.out.print("."); 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); } } // fin LongueTache5 public static void main(String[] args) { SwingUtilities.invokeLater( new Runnable() { public void run() { new GUIetTacheLongue5(); } }); }}

Page 24: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

SwingWorker en interaction avec l’event-dispatcher

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.

Exécution :

Page 25: Badr Benmammar bbm@badr-benmammar.com Thread et Swing

SwingWorker en interaction avec l’event-dispatcher

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(); 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 mettreAJour = new Runnable() { public void run() { barreProgression.setValue(niveau); } };

SwingUtilities.invokeLater(mettreAJour); }

class LongueTache6 extends SwingWorker { public Object construct() { try { for(int i = 0; i < 100; i++ ) { System.out.print("."); setProgression(i); 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); } } // fin LongueTache6 public static void main(String[] args) { SwingUtilities.invokeLater( new Runnable() { public void run() { new GUIetTacheLongue6(); } }); }} // fin GUIetTacheLongue6


Top Related