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)

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

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

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

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

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