IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

AWT/Swing Java Discussion :

Raffraichissement d'une JProgressBar


Sujet :

AWT/Swing Java

  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Mars 2004
    Messages
    71
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2004
    Messages : 71
    Par défaut Raffraichissement d'une JProgressBar
    Bonjour à tous,
    j'ai un projet dans lequel un clic sur un bouton entraine un traitement assez long. Avant ce traitement, je mets une JProgressBar en indeterminated. Le problème est que rien ne se passe, et le bouton reste "freezé".

    J'ai lu sur le net que c'est un problème courant, et qu'il faut jouer avec les threads pour pallier ce problème. J'ai essayé différents exemples de codes, mais aucun ne marche chez moi.

    Pour reproduire le probleme simplement, j'ai recréé un mini projet avec juste le bouton et la progressbar. Le traitement est simulé par un "wait" de 4 secondes.

    Voici mon code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
     
    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
     
    public class Main extends JFrame implements ActionListener{
        private JButton bouton;
        private JProgressBar barre;
        public Main() {
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            this.bouton = new JButton("Click me");
            this.add(this.bouton,BorderLayout.PAGE_START);
            this.barre = new JProgressBar();
            this.add(barre,BorderLayout.CENTER);
            this.setPreferredSize(new Dimension(200,150));
            this.pack();
            this.bouton.addActionListener(this);
            this.setVisible(true);
        }
     
        public void actionPerformed(ActionEvent evt) {
            System.out.println("clique");
            new Runnable() {
                public void run() {
                SwingUtilities.invokeLater(new Runnable()
                {
                    public void run()
                    {
                        barre.setIndeterminate(true);
                    }
                });
                }
            }.run();
            try {Thread.sleep(4000);} catch (Exception e) { }
        }
     
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    new Main();
                }
            });
        }
     
    }
    C'est le genre d'exemple que j'ai trouvé sur le net, mais ca ne fonctionne pas chez moi, la barre reste vide, et ne s'active qu'après les 4 secondes d'attente.

    Pouvez vous m'aiguiller ?

    Merci d'avance.

    eponyme

  2. #2
    Expert confirmé
    Avatar de sinok
    Profil pro
    Inscrit en
    Août 2004
    Messages
    8 765
    Détails du profil
    Informations personnelles :
    Âge : 45
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Août 2004
    Messages : 8 765
    Par défaut
    Tu as probablement mal compris les tutos lus sur le net.

    Je te conseille une lecture approfondie du suivant qui t'expliquera les tenants et aboutissants de la gestion des tâches longues en Swing: http://gfx.developpez.com/tutoriel/j...ing-threading/

    (hints: un thread ne se lance absolument pas de la façon dont tu l'as codé, le runnable doit être fourni à un thread et non appeler directement sa méythode run. De plus ton Thread.sleep est situé au mauvais endroit, tu comprendras pourqoi en lisant le tutoriel).

  3. #3
    Membre confirmé
    Profil pro
    Inscrit en
    Mars 2004
    Messages
    71
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2004
    Messages : 71
    Par défaut
    Merci pour ta réponse.
    J'avais pourtant lu cette doc, mais je m'étais effectivement complètement emmêlé les pinceaux.
    Voici la nouvelle version de l'actionPerformed :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     
        public void actionPerformed(ActionEvent e) {
            new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(4000);
                    } catch (InterruptedException ev) {
                    }
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            barre.setIndeterminate(false);
                        }
                    });
                }
            }).start();
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    barre.setIndeterminate(true);
                }
            });
        }
    J'obtiens bien le résultat que je souhaitais. La barre s'active quand le wait démarre, et se désactive quand il est terminé.

    Une derniere petite question :
    Est ce que la création de la fenêtre, dans le main, doit aussi être faite dans un "invokeLater" ? Je l'avais mis au début, puis j'ai vu que ce n'était pas le cas dans l'exemple de la doc que tu donnes, et je vois que ca marche sans.

    eponyme

  4. #4
    Expert éminent
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Billets dans le blog
    1
    Par défaut
    Salut,

    [edit] En prévisualisant j'ai vu que la réponse avait été trouvé, mais je laisse mon poste plus complet en particulier avec les infos sur le SwingWorker...


    Ce comportement est normal : c'est ton code qui est incorrect !


    Les interfaces Java AWT/Swing fonctionne sur le principe du mono-thread. C'est à dire que toutes les modifications sur l'interface ne doivent se faire que sur un seul et unique thread : l'EDT (Event Dispatch Thread).

    Lorsque tu utilises SwingUtilities.invokeLater() tu exécutes du code dans ce thread. Idem dans les listeners car c'est l'EDT qui les appellera.

    En fait l'EDT est une grosse boucle infini sur ses actions, c'est à dire :
    • Dessiner les composants graphiques.
    • Gérer les évenements (souris, clavier, etc.)
    • Traiter les tâches externes (invokeLater()).


    Si tu effectues une opération longue lors d'une de ces étapes, tu retarde automatiquement l'EDT pour le reste de ses actions...


    Or c'est justement ce que tu fais dans ta méthode actionPerformed() puisque tu fais une pause de 4s... durant lesquels l'EDT est bloqué (et donc ton UI aussi).


    Basiquement tu aurais dû faire quelque chose comme cela :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
        public void actionPerformed(ActionEvent evt) {
            System.out.println("clique");
            barre.setIndeterminate(true);
            new Thread() {
                public void run() {
                    try {Thread.sleep(4000);} catch (Exception e) { }
                }
            }.start(); // On crée et on lance le thread directement
        }
    Ton attente des désormais exécuté dans un thread, et ne bloque plus l'UI...
    Mais il y a deux problèmes : Le bouton est cliquable plusieurs fois (il serait préférable de le griser pendant le traitement), et le Jprogressbar reste en indeterminate à la fin du traitement.

    A la fin du traitement il faudrait restaurer l'interface graphique. Toutefois on ne peut pas faire cela directement dans le thread : il faut le faire dans l'EDT via un invokeLater().
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
        public void actionPerformed(ActionEvent evt) {
            System.out.println("clique dans l'EDT : " + Thread.currentThread().getName());
     
            // On modifie l'état de notre interface graphique
            // (on a le droit on est dans l'EDT)
            bouton.setEnabled(false);
            barre.setIndeterminate(true);
     
            new Thread() {
                public void run() {
                    System.out.println("Debut du traitement dans le thread : "
                            + Thread.currentThread().getName());
                    try {Thread.sleep(4000);} catch (Exception e) { }
                    System.out.println("Fin du traitement dans le thread : "
                            + Thread.currentThread().getName());
     
                    // On utilise invokeLater pour exécuter du code dans l'EDT :
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                             System.out.println("Fin du traitement dans invokeLater : " + Thread.currentThread().getName());
                            // On restaure l'état de notre interface
                            bouton.setEnabled(true);
                            barre.setIndeterminate(false);
                        }
                    });
     
                }
            }.start();
        }
    J'en ai profiter pour rajouter des logs avec le nom des threads, lors d'un clic on obtient cela :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    clique dans l'EDT : AWT-EventQueue-0
    Debut du traitement dans le thread : Thread-2
    Fin du traitement dans le thread : Thread-2
    Fin du traitement dans invokeLater : AWT-EventQueue-0
    AWT-EventQueue-0 est le nom du thread de l'EDT, et Thread-2 est le nom automatique des threads.
    On a bien modifié notre interface dans l'EDT, et effectuer notre traitement dans un thread à part...


    Maintenant, plutôt que d'utiliser des threads, on pourrait utiliser la classe SwingWorker qui est faite pour cela. De prime abord ce n'est pas beaucoup plus simple mais cela a ses avantages :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
        public void actionPerformed(ActionEvent evt) {
            System.out.println("clique dans l'EDT : " + Thread.currentThread().getName());
     
            // On modifie l'état de notre interface graphique
            // (on a le droit on est dans l'EDT)
            bouton.setEnabled(false);
            barre.setIndeterminate(true);
     
            // On crée un SwingWorker, qui se chargera de traiter la tâche.
            // Cette classe est paramétrable via 2 types.
            // + Le premier correspond au résultat final
            // + Le second à un résultat intermédiaire si on utilise publish()
            // -> Ici on ne s'en sert pas, donc on utilise <Voir, Void>
            new SwingWorker<Void, Void>() {
                 // La méthode doInBackground() sera exécuté dans un autre thread,
                 // afin de ne pas gêner le thread gérant l'affichage.
                @Override
                protected Void doInBackground() throws Exception {
                    System.out.println("Debut du traitement en background : "
                            + Thread.currentThread().getName());
                    Thread.sleep(4000);
                    System.out.println("Fin du traitement en background : "
                            + Thread.currentThread().getName());
                    return null;
                }
     
                // La méthode done() est exéxcuté une fois la tâche terminé, mais
                // dans l'EDT. Cela permet de mettre à jour l'interface graphique
                // si besoin...
                @Override
                protected void done() {
                    System.out.println("Fin du traitement dans done() : "
                            + Thread.currentThread().getName());
                    bouton.setEnabled(true);
                    barre.setIndeterminate(false);
                }
            }.execute();  // On crée et on lance le SwingWorker directement
        }

    Un des intérêt du SwingWorker vient du fait qu'il gère la progression (enfin partiellement). Modifions le code afin de gérer cela.
    Plutôt que de faire une attente de 4 secondes, on va faire 100 attentes de 40 ms pour faire augmenter notre barre à chaque étape. Ici c'est tout simple à faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    for (int i=0; i<100; i++) {
                        Thread.sleep(40);
                        setProgress(i);
                    }
    (note : avec d'autres traitements ce pourcentage peut être un peu plus dur à calculer).


    Au final cela nous donnerait ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
        public void actionPerformed(ActionEvent evt) {
            System.out.println("clique dans l'EDT : " + Thread.currentThread().getName());
     
            // On modifie l'état de notre interface graphique
            // (on a le droit on est dans l'EDT)
            bouton.setEnabled(false);
     
            // On crée un SwingWorker, qui se chargera de traiter la tâche.
            // Cette classe est paramétrable via 2 types.
            // + Le premier correspond au résultat final
            // + Le second à un résultat intermédiaire si on utilise publish()
            // -> Ici on ne s'en sert pas, donc on utilise <Voir, Void>
            SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
                 // La méthode doInBackground() sera exécuté dans un autre thread,
                 // afin de ne pas gêner le thread gérant l'affichage.
                @Override
                protected Void doInBackground() throws Exception {
                    System.out.println("Debut du traitement en background : "
                            + Thread.currentThread().getName());
                    for (int i=0; i<100; i++) {
                        Thread.sleep(40);
                        setProgress(i);
                    }
                    System.out.println("Fin du traitement en background : "
                            + Thread.currentThread().getName());
                    return null;
                }
     
                // La méthode done() est exéxcuté une fois la tâche terminé, mais
                // dans l'EDT. Cela permet de mettre à jour l'interface graphique
                // si besoin...
                @Override
                protected void done() {
                    System.out.println("Fin du traitement dans done() : "
                            + Thread.currentThread().getName());
                    bouton.setEnabled(true);
                }
            };
     
            // On ajoute un listener qui va mettre à jour la barre de progression :
            worker.addPropertyChangeListener(new ProgressListener(barre));
            // Puis on démarre notre tâche :
            worker.execute();
        }
    Je disais que la progression était partiellement géré, car il faut implémenter soit-même le listener. Toutefois ce n'est pas bien complexe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class ProgressListener implements PropertyChangeListener {
     
        private final BoundedRangeModel model;
     
        public ProgressListener(BoundedRangeModel model) {
            this.model = model;
        }
        public ProgressListener(JProgressBar progress) {
            this(progress.getModel());
        }
     
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            final String name = evt.getPropertyName();
     
            if ("state".equals(name)) {
                switch( (SwingWorker.StateValue) evt.getNewValue() ) {
                case PENDING:
                case STARTED:
                    this.model.setValue(this.model.getMinimum());
                    break;
                case DONE:
                    this.model.setValue(this.model.getMaximum());
                    break;
                }
            } else if ("progress".equals(name)) {
                Integer value = (Integer)evt.getNewValue();
                this.model.setValue(value.intValue());
            }
        }
    }
    Désormais notre barre de progression se rempli toute seule comme une grande, sans gêner le reste de l'interface graphique



    La classe SwingWorker a d'autres avantages :
    • la valeur de retour de doInBackground() (premier paramètre generics) peut être récupéré dans le done() via un appel à la méthode get().
      On peut donc facilement construire un objet conséquent pour le passer à l'EDT une fois fini.
    • On peut utiliser la méthode publish() (paramétré avec le second param generics) pour publier des données pendant le traitement. Tout ce qu'on passe à publish() sera passé dans le méthode process() dans l'EDT pour les intégrer à l'interface.



    a++

  5. #5
    Expert confirmé
    Avatar de sinok
    Profil pro
    Inscrit en
    Août 2004
    Messages
    8 765
    Détails du profil
    Informations personnelles :
    Âge : 45
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Août 2004
    Messages : 8 765
    Par défaut
    Citation Envoyé par EpOnYmE187 Voir le message
    Une derniere petite question :
    Est ce que la création de la fenêtre, dans le main, doit aussi être faite dans un "invokeLater" ? Je l'avais mis au début, puis j'ai vu que ce n'était pas le cas dans l'exemple de la doc que tu donnes, et je vois que ca marche sans.
    Oui, on est censé le faire. D'ailleurs le Look & Feel substance par exemple lance une exception is la fenêtre n'est pas créée dans l'EDT (cette exception sert de garde fou).

    De fait toute opération concernant les composants Swing doit être faite dans l'EDT, que ce soit l'instanciation ou les appels de méthode (hors précision spécifique dans la javadoc, en effet il existe certaines méthodes qui peuvent être appelées en dehors de l'EDT).

  6. #6
    Membre confirmé
    Profil pro
    Inscrit en
    Mars 2004
    Messages
    71
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2004
    Messages : 71
    Par défaut
    Merci beaucoup pour toutes ces explications.
    J'ai remodifié ma méthode car j'utilisais un invokeLater pour rien pour faire le setIndeterminated au début.
    Ca fonctionne bien.

    merci pour le détail, et pour SwingWorker, je bookmark cette page, ca me servira de référence

    eponyme

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [AJAX] raffraichissement d'une div
    Par wildmary dans le forum Général JavaScript
    Réponses: 1
    Dernier message: 10/01/2007, 15h28
  2. [Ajax] Problème avec le raffraichissement d'une div
    Par Mysti¢ dans le forum Général JavaScript
    Réponses: 1
    Dernier message: 10/08/2006, 21h25
  3. Inserer une jProgressBar dans une Jlist
    Par Noosymer dans le forum Composants
    Réponses: 1
    Dernier message: 18/04/2006, 19h56
  4. Raffraichissement d'une text box
    Par decour dans le forum IHM
    Réponses: 5
    Dernier message: 13/10/2005, 12h31
  5. Raffraichissement d'une jdbTable
    Par Lorenzox dans le forum JBuilder
    Réponses: 5
    Dernier message: 12/06/2005, 18h56

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo