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

EDT/SwingWorker Java Discussion :

problème du pause (sleep) ou problème vitesse d'affichage (Swing)


Sujet :

EDT/SwingWorker Java

  1. #1
    Futur Membre du Club
    Inscrit en
    Mai 2005
    Messages
    6
    Détails du profil
    Informations forums :
    Inscription : Mai 2005
    Messages : 6
    Points : 6
    Points
    6
    Par défaut problème du pause (sleep) ou problème vitesse d'affichage (Swing)
    Hello !!!

    C'est rare que je poste un post sur les forrums ... mais là je suis à bout d'idées !
    Bref,
    Voilà mon soucis,

    J'ai crée une interface graphique pour mon programme, cettte interface me permet
    de gérer plusieurs paramètres du logiciel.
    Sur un des panels je peux lancer et arrêter les services principaux de ce soft!

    un service BS(BS.exe) et BA(BA.exe) ... jusque là tout va bien !
    (je peux arrêter et redémarrer mes services avec le gestionnaire de service windows!)

    Quand je click sur le boutton démarrer ou le boutton arrêter, toujours pas de soucis!
    tous fonctionne! mes services s'arrête et redémarre si je click sur l'un des boutton.
    Dans actionListener des bouttons j'executes des méthodes qui envois des commandes
    externe à java. - net star, et net Stop -
    les voici,

    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
    // une classe Service que j'ai créé pour Dem./Arrê. les services
     
    	public void Demarrage (String NomService)
    	{
    		try
    		{
    			cmd = Runtime.getRuntime().exec(new String [] {"net", "start", NomService,"/y"});
    		}
    		catch (IOException ExpDemarrage) 
    		{
    			ExpDemarrage.printStackTrace();
    		}	
    	}
     
     
    	public void Stop (String NomService)
    	{
    		try
    		{
    			cmd = Runtime.getRuntime().exec(new String [] {"net","stop", NomService,"/y"});
    		}
    		catch (IOException ExpStop) 
    		{
    			ExpStop.printStackTrace();
    		}	
    	}
    mon soucis !

    l'affichage d'une icone sur mon interface.
    Lorsque le service est démarré, j'ai un feux Vert.
    Lorsque le service est arrêté, j'ai un feux Rouge.

    dans un souccis d'efficacité et de sécurité,
    j'ai pensé à mettre un StandBye pendant l'arrêt et le redémarrage des services.
    afficher un feux Orange, et désactiver les buttons (Arrêter/Démarrer)
    pendant le démarrage ou l'arrêt des services.

    j'appelle pour cela une méthode, Attente. de la classe Service Que voici.


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    	public void Attente (long tmp)
    	{
    		try
    		{
    			Thread.sleep(tmp);
    		}
    		catch (InterruptedException e)
    		{
    			e.printStackTrace(); 
    		}
    	}
    elle recoit en paramètre le temps d'attente.
    cela me permet de désactiver les bouton pour que l'utilisateur ne démarre pas
    et arrête aussi tôt les service. et pendant ce temp, j'affiche une icone en
    feux organe!

    OUF! vien enfin ma question,

    >> Service démarré le feu est vert
    >> j'arrête ...
    >> Tout se fige ! mes boutons incacessible (normal! j'ai demandé! mais bon pas complètement désac.)
    >> Le feux ne passe pas à l'orange ! pourtant le temps d'attente est de 5 secondes!
    >> sous windows le service c'est bien arrêté ...
    >> mon soft attent 5 seconde et me rend la main ... LE feu est ROUGE directe!
    Je n'ai pas vu le feux en orange ...

    voilà un bout de l'appli :

    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
    ...
    ImageIcon start = new ImageIcon (getClass().getResource("/img/start.png"));
    ImageIcon pause = new ImageIcon (getClass().getResource("/img/pause.png"));
    ImageIcon stop  = new ImageIcon (getClass().getResource("/img/stop.png"));
     
    // une classe Service que j'ai créé pour Dem./Arrê. les services e géré le temps d'attente.
    Service process = new Service (); 
    ...
    	public void actionPerformed (ActionEvent e)
        	{
        	...
    		if (e.getSource() == jButton1)
        		{
        			jLabel42.setIcon(pause);	// j'affiche l'icone Pause (ORANGE)
        			process.Stop("BS"); 		// je démarre le service BS
        			jButton1.setEnabled(false);	// je désactive le premier bouton
        			jButton2.setEnabled(false);	// je désactive l'autre bouton
        			process.Attente(5000); 		// j'attends 5000 miliSec. (5sec) 
        			jLabel42.setIcon(stop);		// j'affiche l'icone Stop (Rouge)	
        			jButton1.setEnabled(true);	// je Réactives les boutons
        			jButton2.setEnabled(true);	// je Réactives les boutons
    		}
     
    	...
    	}
    dans le fond tout fonctionne ... seulement tout se fige ... pendant la pause ...
    et le ne vois pas mon ... icone orange ...
    je suis sur que le service démarre ... juste par ce que à ce moment là c'est plus JAVA
    qui gère ... ma commande est passé à l'OS ... et c'est OS qui gere ... mon arrêt du service ...
    Les boutons ne sont pas désactivé ... il sont tout simplement bloqué ...
    car je ne les vois pas grisé ... (juste inacessible !)

    Est-ce une bonne méthode pour faire une pause ... dans le programme ... sans boufé de la resource??
    Car je soupsonne plus la méthde d'attente ... qui est Thread qui s'execute.
    mais j'ai bien demandé à mon icone de changé d'adord !
    ensuite désactiovation des bouton ...
    et ensuite la pause ...
    Hors ... j'ai l'impression que ... pas le temps ... de réagir ... que déjà le Thread s'execute !

    TOUT SE COMPILE PARFAITEMENT !


    que dois je faire ... MERCI !

    Avis aux solutions ... ???

    Voici un appercu de l'interface :

  2. #2
    Expert confirmé
    Avatar de le y@m's
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2005
    Messages
    2 636
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Février 2005
    Messages : 2 636
    Points : 5 943
    Points
    5 943
    Par défaut
    Je te conseil vivement la lecture de ce tutoriel qui me semble correspondre à ta problématique :
    Threads et performance avec Swing.
    Je ne répondrai à aucune question technique par MP.

    Pensez aux Tutoriels et aux FAQs avant de poster (pour le java il y a aussi JavaSearch), n'oubliez pas non plus la fonction Rechercher.
    Enfin, quand une solution a été trouvée à votre problème
    pensez au tag

    Cours Dvp : http://ydisanto.developpez.com
    Blog : http://yann-disanto.blogspot.com/
    Page perso : http://yann-disanto.fr

  3. #3
    Expert éminent sénior
    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
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Salut,



    Ton problème vient de l'EDT !
    L'EDT (Event Dispatch Thread) est le thread qui s'occupe de gérer l'affichage et les evenements de l'interface graphique. C'est ce seul thread qui doit être utilisé lorsque tu modifies l'interface graphique. Et c'est également lui qui gère les évenements (clic de souris, clavier, etc) et les raffraichissements de l'affichage...

    Ce thread utilise une pile de traitement pour cela, mais s'il est bloqué par une tâche trop longue, ton interface sera figé...

    Dans ton cas la méthode actionPerformed() est appelée par ce thread puisqu'il s'agit d'un evenement. Tu peux vérifier cela avec la méthode SwingUtilities.isEventDispatchThread().

    Or comme tu fais le sleep() dans l'EDT, ce dernier ne peut pas mettre à jour l'affichage. Ainsi dans ton code :
    • Tu désactives tes boutons avec setEnabled(false) et tu changes ton icône, ce qui est bien fait au niveau de la mémoire mais pas de l'interface graphique. En réalité les composants "demandent" à l'EDT d'être redéssiné le plus tôt possible...
    • Tu bloques l'EDT avec le b]sleep()[/b] : aucun raffraichissement ne peux donc avoir lieu pendant ce temps...
    • Tu réactives tes boutons et restaures ton icône. Encore une fois les composants "demandent" à l'EDT d'être redéssiné...


    Une fois que ta méthode actionPerformed() est terminée, l'EDT peut reprendre ses tâches normales, et s'occupera donc de redéssiner les composants...

    l'état désactivé et l'icone orange associé ne seront donc jamais visible...


    En fait il faut que tu exécutes tes traitements dans un autre thread et de modifier l'interface avec SwingUtilities.invokeLater(). Mais le plus simple étant d'utiliser la classe SwingWorker.

    La classe SwingWorker a été introduite dans Java 6 en standard, mais il existe une version backport pour Java 5.0 (https://swingworker.dev.java.net/) 100% compatible et une vielle version dans les tutoriels de Sun (http://java.sun.com/products/jfc/tsc.../threads1.html).

    Cette dernière version est un peu moins complète (et ne possède pas les mêmes noms de méthodes) mais elle a l'avantage d'être compatible avec des JDK plus ancien...



    La classe SwingWorker propose principalement deux méthodes à surcharger :
    [list][*]doInBackground(), qui doit contenir le code à exécuter dans une tâche de fond (c-a-d dans un thread séparé).[*]done(), qui sera appelé par l'EDT une fois que la tâche sera terminé. Typiquement cette méthode permet de mettre à jours l'affichage après le traitement...


    Ainsi ta méthode actionPerformed devrait plus ressembler à 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
    	public void actionPerformed (ActionEvent e)
    	{
    		if (e.getSource() == jButton1) {
    			// On est dans l'EDT, on modifie les attributs des composants :
    			jLabel42.setIcon(pause);	// j'affiche l'icone Pause (ORANGE)
    			jButton1.setEnabled(false);	// je désactive le premier bouton
    			jButton2.setEnabled(false);	// je désactive l'autre bouton
     
    			SwingWorker worker = new SwingWorker() {
     
    				// Ce traitement sera exécuté dans un autre thread :
    				protected Object doInBackground() throws Exception {
    					process.Stop("BS"); 		// je démarre le service BS
    					process.Attente(5000); 		// j'attends 5000 miliSec. (5sec) 
    					return null;
    				}
     
    				// Ce traitement sera exécuté à la fin dans l'EDT 
    				protected void done() {
    					// On est dans l'EDT, on restaure les attributs des composants :
    					jLabel42.setIcon(stop);		// j'affiche l'icone Stop (Rouge)	
    					jButton1.setEnabled(true);	// je Réactives les boutons
    					jButton2.setEnabled(true);	// je Réactives les boutons
    				}
    			};
     
    			// On lance l'exécution de la tâche:
    			worker.execute();
    		}
    	}

    Ainsi désormais ta méthode actionPerformed est très rapide (et ne bloque plus pendant 5 secondes) et redonne donc la main à l'EDT pour actualiser l'affichage de ton interface et gérer les autres évenements... Pendant que la méthode doInBackground() s'exécute dans un autre thread.


    La gestion des threads n'est pas forcément évidente à comprendre... mais avec Swing on peut dire qu'il y a deux règles simples :
    • Toutes les modifications sur les composants ou l'interface graphique doit être exécuté dans l'EDT.
    • Tous les traitements dans l'EDT doivent être rapide afin de ne pas le bloquer trop longtemps.



    Pour plus d'info sur le sujet, je te conseille de lire l'article de Romain Guy : Threads et performance avec Swing

    Si l'anglais ne te fait pas peur, tu peux jeter un coup d'oeil au tutoriel de Sun (mis à jours pour Java 6) : http://java.sun.com/docs/books/tutor.../dispatch.html

    Enfin, surveilles bien l'index de la section Java... mon petit doit me dit qu'un article sur le sujet ne devrait pas tarder


    a++


    PS : dernier détail, si tu n'utilises pas les flux d'entrée/sortie pour communiquer avec le process que tu lances, il vaut mieux les fermer :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    cmd = Runtime.getRuntime().exec(new String [] {"net","stop", NomService,"/y"});
    cmd.getInputStream().close();
    cmd.getOutputStream().close();
    cmd.getErrorStream().close();
    PS 2 : Whoua j'ai encore posté un pavé... j'espère que cela te sera utile

  4. #4
    Membre à l'essai
    Inscrit en
    Novembre 2006
    Messages
    31
    Détails du profil
    Informations forums :
    Inscription : Novembre 2006
    Messages : 31
    Points : 15
    Points
    15
    Par défaut
    Bonjour MAD_Tarik,

    Je suis loin d'être un crack car ça fait seulement deux mois que je programme en java. Et comme toi je me suis confronté à ce genre de problème.

    Tous les conseils que je trouvais étaient soit inefficaces (genre ajouter une méthode validate() ou repaint() ...) soit trop compliqué pour moi (genre gestion des thread ...).

    En faisant des essais tout seul comme un grand j'ai fini par tombé sur un truc qui marche. Mais peut être il ne faut pas le faire ???

    Si tu veux essayer, rajoute la ligne "this.update(this.getGraphics());" dans ta méthode "ActionPermormed" comme 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
    public void actionPerformed (ActionEvent e)
            {
            ...
            if (e.getSource() == jButton1)
                {
                    jLabel42.setIcon(pause);    // j'affiche l'icone Pause (ORANGE)
                    process.Stop("BS");         // je démarre le service BS
                    jButton1.setEnabled(false);    // je désactive le premier bouton
                    jButton2.setEnabled(false);    // je désactive l'autre bouton
                    this.update(this.getGraphics());  // raffraichissement de l'interface
                    process.Attente(5000);         // j'attends 5000 miliSec. (5sec) 
                    jLabel42.setIcon(stop);        // j'affiche l'icone Stop (Rouge)    
                    jButton1.setEnabled(true);    // je Réactives les boutons
                    jButton2.setEnabled(true);    // je Réactives les boutons
            }
     
        ...
        }
    Normalement, ça marche bien.

  5. #5
    Expert éminent sénior
    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
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par patanoc
    Normalement, ça marche bien.
    Pas tout à fait !
    Tu forces bien l'affichage de ta fenêtre, mais elle restera bloqué pendant les 5 secondes de pause qui suivent : tous les menus / bouton / effet de survol parraitront bloqués pendant ce temps... Pire tu peux te retrouver avec le bug du "rectangle-gris", c'est à dire que si une fenêtre vient se placer devant la tienne, alors elle ne sera plus affiché et se contentera d'une rectangle gris...

    Bref ton application ne sera pas très réactive !
    Lorsque tu developpes une application graphique, tu es obligé de gérer des threads pour les traitements "lourd"... et franchement ce n'est pas très dur d'utiliser SwingWorker...

    a++

  6. #6
    Membre à l'essai
    Inscrit en
    Novembre 2006
    Messages
    31
    Détails du profil
    Informations forums :
    Inscription : Novembre 2006
    Messages : 31
    Points : 15
    Points
    15
    Par défaut
    adiGuba,

    Je comprends tout à fait ton point de vue.

    Maintenant, si le programme doit attendre 5 seconde, pourquoi premettre l'accès aux menus et boutons alors qu'il faut attendre 5 seconde avant de faire autre chose. Si l'utilisateur peut lancer une autre tache alors que le traitement long n'est pas fini, ça peut planter le programme car tout n'est pas dans un état stable.

    Pour le bug du rectangle gris, je n'était pas au courant. Mais la meilleure solution serait peut être de corrigé ce bug dans la bibliothèque SWING afin de nous simplifier le codage. Si ce bug ne peut pas être corrigé, c'est peut être que SWING a des gros défauts de conception à la base ???

    Comme je viens de débuter en java, j'ai sans doute tort d'écrire ces lignes mais j'aimerais bien comprendre pourquoi une ligne de code qui permet de régler un problème d'affichage ne devrait pas être utilisée au profit de solutions plus lourdes (pour éviter de dire dur).

    Merci d'avance en tous cas de tes éclaicissements !

    A+

  7. #7
    Expert éminent sénior
    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
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par patanoc
    Maintenant, si le programme doit attendre 5 seconde, pourquoi premettre l'accès aux menus et boutons alors qu'il faut attendre 5 seconde avant de faire autre chose. Si l'utilisateur peut lancer une autre tache alors que le traitement long n'est pas fini, ça peut planter le programme car tout n'est pas dans un état stable.
    Je n'ai pas dit qu'il ne fallait pas désactiver certaines actions... mais de là à bloquer tout le programme il y a une marge !

    Ici l'attente de 5 secondes sert uniquement à modifier une icône et réactiver deux boutons, et je ne pense pas que l'utilisateur comprenne qu'il faille pour cela bloquer tout le programme (y compris menu, onglet, et zone de saisie).




    Citation Envoyé par patanoc
    Pour le bug du rectangle gris, je n'était pas au courant. Mais la meilleure solution serait peut être de corrigé ce bug dans la bibliothèque SWING afin de nous simplifier le codage. Si ce bug ne peut pas être corrigé, c'est peut être que SWING a des gros défauts de conception à la base ???
    Ce n'est pas un bug de Swing !!! C'est un bug de programmation !

    Depuis toujours Swing est mono-thread, et toute les modifications de l'interface doivent passé par l'EDT, alros que les traitements doivent être effectué dans des threads séparé. Si tu ne respectes pas cela il n'y a rien d'étonnant à avoir un bug !

    Pour info le même principe existe dans quasiment toutes les API graphique.

    Pour éviter cela il faudrait que tous les composants/classes soit thread-safe, ce qui compliquerait énormément la création de composant Swing !

    Toutefois, le bug du "rectangle gris" est tellement répandu qu'il a été en partie pris en charge avec Java 6 : au lieu d'afficher un rechtangle gris, c'est le dernier état de l'application qui est affiché, mais logiquement cela ne change en rien le fait que l'application soit quand même bloqué...

    Citation Envoyé par patanoc
    Comme je viens de débuter en java, j'ai sans doute tort d'écrire ces lignes mais j'aimerais bien comprendre pourquoi une ligne de code qui permet de régler un problème d'affichage ne devrait pas être utilisée au profit de solutions plus lourdes (pour éviter de dire dur).
    Tout simplement parce que l'interface graphique restera figé pendant tout le traitement, donc ici pendant 5 secondes, et qu'une application ne devrait pas se permettre de tel latence !

    Puisque tu débutes il vaudrait mieux en profiter pour coder quelque chose de propre plutôt que d'utiliser des "rustines" pour donner un faux-semblant de résultat correcte...


    Et l'utilisation de SwingWorker n'est pas vraiment beaucoup plus dur !


    a++

  8. #8
    Membre à l'essai
    Inscrit en
    Novembre 2006
    Messages
    31
    Détails du profil
    Informations forums :
    Inscription : Novembre 2006
    Messages : 31
    Points : 15
    Points
    15
    Par défaut
    OK !

    Merci pour tes explications et conseils.

    MAD_Tarik, désolé de t'avoir refilé une rustine

    Apparemment la grosse cavalerie du multi-threading est inévitable pour une appli pro avec SWING ...

    A+

  9. #9
    Membre confirmé Avatar de broumbroum
    Profil pro
    Inscrit en
    Août 2006
    Messages
    406
    Détails du profil
    Informations personnelles :
    Âge : 40
    Localisation : Suisse

    Informations forums :
    Inscription : Août 2006
    Messages : 406
    Points : 465
    Points
    465
    Par défaut
    une fois tu mets "résolu" pour moi....

  10. #10
    Expert éminent sénior
    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
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Et comme une longue explication ne vaut rien à coté d'un exemple pratique, je vous propose une petite application simple pour démontrer cela.

    Il s'agit d'une simple application composé d'une animation, de deux boutons et d'une barre de menu avec plusieurs items.
    • L'animation est tout simplement un JLabel avec un GIF animé ( qui vient de https://duke.dev.java.net/ ).
    • Lors d'un clic, les deux boutons effectueront une action qui prendra 10 secondes (un simple sleep() en fait - j'ai utilisé 10 secondes pour que ce soit bien perceptible).
    • Quand aux items du menu, il se contenteront d'afficher leur nom sous l'animation (cela permet simplement de voir la réactivité de l'interface).



    Le premier bouton ("10s (avec update())") utilise update(getGraphics()) pour forcer le bouton a être actualisé avant le traitement. Voici le code exact de l'actionPerformed() :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    	public void actionPerformed(ActionEvent e) {
    		button1.setEnabled(false); // je désactive le bouton
    		update(getGraphics());  // raffraichissement de l'interface
    		attente();         // j'attends 5000 miliSec. (5sec) 
    		button1.setEnabled(true);    // je réactives le bouton
    	}
    Le second bouton ("10s (avec SwingWorker)") utilise SwingWorker pour lancer le traitement en tâche de fond. Voici son 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
    	public void actionPerformed(ActionEvent e) {
    		button2.setEnabled(false); // je désactive le bouton
     
    		new SwingWorker() {
    			@Override
    			protected Object doInBackground() throws Exception {
    				// Traitement effectué en tâche de fond
    				attente();
    				return null;
    			}
    			@Override
    			protected void done() {
    				// Mise à jours de l'interface à la fin du traitement
    				button2.setEnabled(true);
    			}
    		}.execute(); // Exécution de la tâche
    	}
    C'est vrai que le code est un peu plus long, mais il est bien décomposé en trois partie :
    • La modification de l'interface lors de l'action (le code de actionPerformed())
    • Le traitement à exécuter en tâche de fond (le code de doInBackground())
    • La mise à jour de l'affichage à la fin du traitement (le code de done())

    Et la lourdeur du code est surtout dû à une syntaxe très verbeuse (au passage j'espère que les closures seront un jours intégré dans le langage ), mais en lui même le code n'est pas vraiment plus compliqué ! Il suffit de ne pas oublier d'exécuter le SwingWorker...


    RESULTAT


    Que se passe-t-il lorsque je clique sur le bouton "10s (avec update())" ? Le bouton est bien grisé, et redevient normal 10 secondes plus tard à la fin du traitement. Tout pourrait sembler correct, mais cela a des effets sur l'interface pendant toute l'exécution du traitement :
    • L'animation se bloque
    • Le menu et les autres composants sont bloqué (ils ne répondent pas) alors qu'il n'ont pas été désactivé.
    • Les redimensionnements de la fenêtre ne fonctionnent pas, ou plutôt de manière partielle (la fenêtre est bien redimensionnée par le système, mais son contenu ne s'adapte pas.
    • On ne peut pas fermer le programme via la croix de fermeture...
    • Si on déplace une autre fenêtre par dessus notre programme, on obtient le bug du rectangle gris !

    En fait, tous ces traitements/actualisation de l'affichage ne seront effectué par l'EDT qu'à la fin du traitement de notre bouton...


    Maintenant que se passe-t-il lorsque je clique sur le bouton "10s (avec SwingWorker)" ? Le bouton est bien grisé, et redevient normal 10 secondes plus tard à la fin du traitement. Mais le reste de l'application reste fonctionnel - tout est réactif et rien n'est bloqué !



    Vous pouvez tester cela par vous même en lançant l'application via Java Web Start en cliquant sur ce lien : Demo SwingWorker (Java 5.0 minimum requis).

    Ou alors en téléchargeant manuellement l'archive jar et en l'exécutant : DemoSwingWorker.jar
    (note : il vous faut alors télécharger séparément l'archive swing-worker.jar sur https://swingworker.dev.java.net/ et la placer dans le même répertoire).

    Pour info le code source complet est disponible ici : DemoSwingWorker.java


    A vous de tester et de faire votre choix !

    a++

  11. #11
    Futur Membre du Club
    Inscrit en
    Mai 2005
    Messages
    6
    Détails du profil
    Informations forums :
    Inscription : Mai 2005
    Messages : 6
    Points : 6
    Points
    6
    Par défaut
    Hello Tout le monde !!!

    Hé oui depuis dimanche j'ai disparu !
    et vous savez quoi ?
    NORMAL ... à fond dans "Threads et performance avec swing" ! A lire absolument ...
    Pour tout revoir sur les Threads.

    Partant de la problèmatique des threads tout court vers celle des thread avec swing ...
    Je vous propose ce TD, de la FAC ... pas mal non-plus !
    Swing et Thread en frenchie en plus ...
    Et grace à ces TD on comprend bien pourquoi SwingWorker est arrivé!
    C'est le ZORO, du SWING ...

    J'ai réussi à modifier quelques morceaux de mon prog... et ca à l'aire de fonctionner...
    je reforme tout cela ce soir ... je compile, et je met ca en ligne ...
    (si tout marche comme prévu !)

    Le soft un gestionaire de performances pour les serveur et les grands parc info ... Gestion des ressources sytèmes (Processeur, Disques, services ... ect), conversion des evenement windows en trap SNMP ect ... gestion des alertes par mail ... le tout en utilisant SNMP ...

    A ce soir ... peut-être si tout fonctionne bien !

    Merci à tous pour vos commentaires !
    Merci à toi adiGuba, pour m'avoir mis sur la piste !
    Merci patanoc ... pour le Bricolage !

    CHUZE !

  12. #12
    Futur Membre du Club
    Inscrit en
    Mai 2005
    Messages
    6
    Détails du profil
    Informations forums :
    Inscription : Mai 2005
    Messages : 6
    Points : 6
    Points
    6
    Par défaut SwingWorker ... le sauveur...
    Bonjour à tous,

    Premièrement je reviens sur le BUG DU RECTANGLE GRIS, c'est vraiement pas un myte !
    J'ai fait mon test avec les autres "alternatives bricolage" de thread ... et je me tape des rectangle gris, et/ou des bouttons qui ne répondent plus à la souris un certains moments ...

    Enfin le test avec la méthode SwingWorker ... est inconstestable!

    je rappel le contexte :
    Dans mon appli j'ai 4 bouttons, un bouton démarrer/un bouton arrêter pour chaque arrêt/démarrage de service. Il ya deux services. service BSAgent et service SNMP.
    Pour éviter que les services soient arrêtés/démarrés sucessivement par l'utilisateur et notament laisser le temp aux services de démarrer, on désactive les boutons Démarrer/Arrêter pendant 10 secondes.
    Les services, sont dépendant, donc la totalité des boutons sont désactivés momentanément pendant l'action ...
    Une animation est aussi proposée ... le basculement d'un feux selon l'arrêt, le démarrage du service et la la gestion de la pause.

    Feux ROUGE : Service arrêté.
    Feux ORANGE : Service en cours de démarrage ou en cours d'arrêt(Pause).
    Feux VERT : Service démarré.

    La gestion de ces services est gérée par une classe appellée, {Service}
    Elle contient une méthode Demarrage("ServiceName") pour démarré le service,
    Stop("ServiceName") pour arrêté le service, isStarted("ServiceName") pour verrifier si le service est bien démarré renvois un bollean.

    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
     
     
    import java.io.*;
     
    public class Service
    {
    	static boolean EtatService = false;
    	Process cmd;
     
    	// METHODE DE CONTROLE : RENVOIS TRUE OU FALSE SELON SI LE SERVICE EST DEMARRE OU PAS.
    	public boolean isStarted (String NomService) 
    	{
      		try 
      		{
    		    cmd = Runtime.getRuntime().exec("net start");
    		    BufferedReader bos = new BufferedReader(new InputStreamReader(cmd.getInputStream()));
    		    String line = new String ();
     
    			// LE TEXTE DE SORTIE DU [net start] EST RÉCUPÉRÉ LIGNE PAR LIGNE.
    			while ((line = bos.readLine()) != null) 
    			{
    				// EFFECTUE LE TRAITEMNET DE CHAQUE LIGNE DE TEXTE RÉCUPÉRÉE.
    				line = line.trim();
    				if (line.equalsIgnoreCase(NomService))
    				{
    					EtatService = true;
    				}
    			}
    		}
     
    		catch (IOException ExpEtatService)
    		{
    			ExpEtatService.printStackTrace();
    		}
     
    		return EtatService;
    	} 
                 // METHODE PERMETANT LE DEMARRAGE DU SERVICE DEMANDE.
    	public void Demarrage (String NomService)
    	{
    		try
    		{
    			cmd = Runtime.getRuntime().exec(new String [] {"net", "start", NomService,"/y"});
    		}
    		catch (IOException ExpDemarrage) 
    		{
    			ExpDemarrage.printStackTrace();
    		}	
    	}
     
    	// METHODE PERMETANT L'ARRET DU SERVICE DEMANDE.
    	public void Stop (String NomService)
    	{
    		try
    		{
    			cmd = Runtime.getRuntime().exec(new String [] {"net","stop", NomService,"/y"});
    		}
    		catch (IOException ExpStop) 
    		{
    			ExpStop.printStackTrace();
    		}	
    	}
    }
    Pas de soucis ... pour cette classe ...
    Juste que je dois comme l'a signalé adiGuba, fermer le stream cmd
    cmd.getInputStream().close();
    cmd.getOutputStream().close();
    cmd.getErrorStream().close();

    Revenons à nos moutons... une partie de l'interface GUI qui va inter-réagir avec ça :

    Voilà le code que j'ai adopté ... ca fonctionne ! qu'on me dise si quelqu'un y vois un inconvéniant !

    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    public class ConfigBSA extends JFrame implements ActionListener
    {
    private Service	process;
    private SwingWorker worker ;
    private String	EtatServ;
    ... ...
    private ImageIcon 	start;
    private ImageIcon 	pause;
    private ImageIcon 	stop;
    private JLabel        jLabel42;
    private JLabel        jLabel43;
    private JButton 	jButton1;
    private JButton 	jButton2;
    private JButton 	jButton3;
    private JButton 	jButton4;
    ... ...
     
    public static void main(String args[]) 
    { 
    // LECTURE DU FICHIER CONFIG DE BSA
    lectureFch = new LectCfgBSA();
    lectureFch.LectCfgBSA();
     
     // LANCEMENT DE L'APPLICATION GRAPHIQUE - GUI
    EventQueue.invokeLater(new Runnable() 
    {public void run() {new ConfigBSA().setVisible(true);}});
    }
     
    public ConfigBSA() 
    {    initComponents();   }
     
     
    // GESTION ET PARAMETRAGE DES COMPOSANTS - MISE EN FORME GUI
    private void initComponents() 
    {
    GridBagConstraints gridBagConstraints;
    process = new Service ();
     
    // Initialisation des composants ...
    jButton2 = new JButton();
    jButton3 = new JButton();
    jButton4 = new JButton();
    jLabel42 = new JLabel();
    jLabel43 = new JLabel();
    start = new ImageIcon (getClass().getResource("/img/start.png"));
    pause = new ImageIcon (getClass().getResource("/img/pause.png"));
    stop  = new ImageIcon (getClass().getResource("/img/stop.png"));
     
    ... /*GESTION DU POSITIONNEMENT DES CHAMPS*/ ...
     
    public void actionPerformed (ActionEvent e)
    {
    ... /*GESTION DES AUTRE BOUTONS ET MENU ITEMS / TACHES NON LONGUES*/ ...
    ...
    ...
     
    ... /*GESTION DES BOUTONS AVEC TACHES LONGUES*/....
     
    if (e.getSource() == jButton1)
    {
    EtatServ = "BSAgentDemarrage";
    jLabel42.setIcon(pause);
    jLabel43.setIcon(pause);
    jButton1.setEnabled(false);
    jButton2.setEnabled(false);
    jButton3.setEnabled(false);
    jButton4.setEnabled(false);
    process.Demarrage("BSAgent");
    worker = new GestionPause(10000);
    worker.start();
    }
     
    if (e.getSource() == jButton2)
    {
    EtatServ = "BSAgentArret";
    jLabel42.setIcon(pause);
    jButton1.setEnabled(false);
    jButton2.setEnabled(false);
    jButton3.setEnabled(false);
    jButton4.setEnabled(false);
    process.Stop("BSAgent");
    worker = new GestionPause(10000);
    worker.start();
    }
     
    if (e.getSource() == jButton3)
    {
    EtatServ = "SNMPDemarrage";
    jLabel43.setIcon(pause);
    jButton1.setEnabled(false);
    jButton2.setEnabled(false);
    jButton3.setEnabled(false);
    jButton4.setEnabled(false);
    process.Demarrage("SNMP");
    worker = new GestionPause(10000);
    worker.start();
    }
     
    if (e.getSource() == jButton4)
    {
    EtatServ = "SNMPArret";
    jLabel42.setIcon(pause);
    jLabel43.setIcon(pause);
    jButton1.setEnabled(false);
    jButton2.setEnabled(false);
    jButton3.setEnabled(false);
    jButton4.setEnabled(false);
    process.Stop("SNMP");
    worker = new GestionPause(10000);
    worker.start();
    }
    }
     
    class GestionPause extends SwingWorker
    {
    private int TmpPause;
    public GestionPause (int tmp)
    {
    super ();
    TmpPause = tmp;
    }
     
    public Object construct ()
    {
    try {	Thread.sleep(TmpPause);	}
    catch (InterruptedException expPause){}
    return null;
    }
     
    public void finished ()
    {
    jButton1.setEnabled(true);
    jButton2.setEnabled(true);
    jButton3.setEnabled(true);
    jButton4.setEnabled(true);
     
    if (EtatServ.equals("BSAgentDemarrage"))
    {
    	jLabel42.setIcon(start);
    	jLabel43.setIcon(start);
    }
     
    if (EtatServ.equals("BSAgentArret"))
    {
    	jLabel42.setIcon(stop);
    }
     
    if (EtatServ.equals("SNMPDemarrage"))
    {
    	jLabel43.setIcon(start);
    }
     
    if (EtatServ.equals("SNMPArret"))
    {
    	jLabel42.setIcon(stop);
    	jLabel43.setIcon(stop);
    }
     
    }
    }
    }
    ca donne ça ... aprés compile ...


    ...

    J'ai rencontré un soucis avec la class que m'a proposé de télécharger adiGuba, sur le site de https://swingworker.dev.java.net/ ...
    j'en ai pris une autre ...
    elle ne me parraisse, pas pareille dans leurs codes ... elle est modifiée ...
    et du coup on ne retrouve pas les toutes les méthodes.

    voici la class SwingWorker que j'ai récupéré ... à parament, avec l'autre ...
    mon compilateur à fait la geul ... il n'aime pas le "@" ...
    je n'ai pas eu le temps de m'y penché à fond ... je vous reporterai ...
    les erreurs que j'ai eu ... il faut dire ... qu'en ayant lu toutes les infos SwingWorker ... je pourrait peut-être me débrouillé seul ... maintenant
    je vous tiens au courant tout de même!

    Voici la class SwingWorketr que j'ai récupéré ... pas de méthode doInBackground() ! ? ? ? elle me parait peu complète ... mais elle fonctionne!

    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
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    import javax.swing.SwingUtilities;
     
    /**
     * This is the 3rd version of SwingWorker (also known as
     * SwingWorker 3), an abstract class that you subclass to
     * perform GUI-related work in a dedicated thread.  For
     * instructions on and examples of using this class, see:
     * 
     * http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html
     *
     * Note that the API changed slightly in the 3rd version:
     * You must now invoke start() on the SwingWorker after
     * creating it.
     */
    public abstract class SwingWorker {
        private Object value;  // see getValue(), setValue()
     
        /** 
         * Class to maintain reference to current worker thread
         * under separate synchronization control.
         */
        private static class ThreadVar {
            private Thread thread;
            ThreadVar(Thread t) { thread = t; }
            synchronized Thread get() { return thread; }
            synchronized void clear() { thread = null; }
        }
     
        private ThreadVar threadVar;
     
        /** 
         * Get the value produced by the worker thread, or null if it 
         * hasn't been constructed yet.
         */
        protected synchronized Object getValue() { 
            return value; 
        }
     
        /** 
         * Set the value produced by worker thread 
         */
        private synchronized void setValue(Object x) { 
            value = x; 
        }
     
        /** 
         * Compute the value to be returned by the <code>get</code> method. 
         */
        public abstract Object construct();
     
        /**
         * Called on the event dispatching thread (not on the worker thread)
         * after the <code>construct</code> method has returned.
         */
        public void finished() {
        }
     
        /**
         * A new method that interrupts the worker thread.  Call this method
         * to force the worker to stop what it's doing.
         */
        public void interrupt() {
            Thread t = threadVar.get();
            if (t != null) {
                t.interrupt();
            }
            threadVar.clear();
        }
     
        /**
         * Return the value created by the <code>construct</code> method.  
         * Returns null if either the constructing thread or the current
         * thread was interrupted before a value was produced.
         * 
         * @return the value created by the <code>construct</code> method
         */
        public Object get() {
            while (true) {  
                Thread t = threadVar.get();
                if (t == null) {
                    return getValue();
                }
                try {
                    t.join();
                }
                catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // propagate
                    return null;
                }
            }
        }
     
     
        /**
         * Start a thread that will call the <code>construct</code> method
         * and then exit.
         */
        public SwingWorker() {
            final Runnable doFinished = new Runnable() {
               public void run() { finished(); }
            };
     
            Runnable doConstruct = new Runnable() { 
                public void run() {
                    try {
                        setValue(construct());
                    }
                    finally {
                        threadVar.clear();
                    }
     
                    SwingUtilities.invokeLater(doFinished);
                }
            };
     
            Thread t = new Thread(doConstruct);
            threadVar = new ThreadVar(t);
        }
     
        /**
         * Start the worker thread.
         */
        public void start() {
            Thread t = threadVar.get();
            if (t != null) {
                t.start();
            }
        }
    }
    Merci encore...
    et j'attends vos commentaires ...

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

    Informations forums :
    Inscription : Août 2004
    Messages : 8 765
    Points : 12 977
    Points
    12 977
    Par défaut
    La version officielle de SwingWorker (lien qu'Adiguba t'as filé) n'est compatible qu'avec java 5 et ultérieurs. Au vu du message d'erreur que tu as tu utilises un java pré annotations donc pré java 5.

    Accessoirement pour SwingWorker il est présent de base en java 6
    Hey, this is mine. That's mine. All this is mine. I'm claiming all this as mine. Except that bit. I don't want that bit. But all the rest of this is mine. Hey, this has been a really good day. I've eaten five times, I've slept six times, and I've made a lot of things mine. Tomorrow, I'm gonna see if I can't have sex with something.

  14. #14
    Expert éminent sénior
    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
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par MAD_Tarik
    J'ai rencontré un soucis avec la class que m'a proposé de télécharger adiGuba, sur le site de https://swingworker.dev.java.net/ ...
    j'en ai pris une autre ...
    elle ne me parraisse, pas pareille dans leurs codes ... elle est modifiée ...
    et du coup on ne retrouve pas les toutes les méthodes.
    En effet il existe plusieurs versions de SwingWorker :
    • Celle en standard dans Java 6.
    • Celle du site https://swingworker.dev.java.net/ qui est un backport (portage vers une version plus ancienne) destiné à Java 5.0 seulement.
    • Enfin, celle que tu as proviens du tutoriel de Sun, et correspond à la classe initiale.[/url]

    Les versions Java 6 / 5.0 sont plus complète car elle bénéficie de l'API de de concurrence apparut dans Java 5.0

    Citation Envoyé par MAD_Tarik
    il n'aime pas le "@" ...
    je n'ai pas eu le temps de m'y penché à fond ... je vous reporterai ...
    les erreurs que j'ai eu ...
    Il ne gère pas les annotations de Java 5.0 : tu dois utiliser une version de Java plus ancienne, ou bien compiler avec un mode de compatibilité de source inférieur...

    Citation Envoyé par MAD_Tarik
    Voici la class SwingWorketr que j'ai récupéré ... pas de méthode doInBackground() ! ? ? ? elle me parait peu complète ... mais elle fonctionne!
    Les méthodes principales (doInBackground() et done()) sont bien présente mais avec un nom différent (construct() et finished()), et même si elle est moins complète, le principal es bien là


    a++

  15. #15
    Expert éminent sénior
    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
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Juste pour info, et pour ceux qui l'aurait loupé sur l'index : Des interfaces graphiques plus performantes avec SwingWorker, par Romain Vimont

    a++

  16. #16
    Membre expert
    Avatar de ®om
    Profil pro
    Inscrit en
    Janvier 2005
    Messages
    2 815
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2005
    Messages : 2 815
    Points : 3 080
    Points
    3 080
    Par défaut
    Citation Envoyé par MAD_Tarik
    Pour éviter que les services soient arrêtés/démarrés sucessivement par l'utilisateur et notament laisser le temp aux services de démarrer, on désactive les boutons Démarrer/Arrêter pendant 10 secondes.
    Les services, sont dépendant, donc la totalité des boutons sont désactivés momentanément pendant l'action ...
    Une bonne idée lorsqu'il y a beaucoup de choses à désactiver, c'est de mettre un "filtre" transparent (éventuellement partiellement) sur l'interface, qui intercepte les clics de la souris (sur le GlassPane).

    Et éventuellement, en plus, mettre une animation, mais là c'est un peu plus compliqué:

    http://gfx.developpez.com/tutoriel/java/swing/wait/

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

Discussions similaires

  1. Problème avec fonction sleep
    Par pitxu dans le forum Apache
    Réponses: 2
    Dernier message: 07/02/2008, 03h27
  2. problème sur un sleep
    Par dr4g0n dans le forum Linux
    Réponses: 3
    Dernier message: 22/01/2008, 21h00
  3. Problème avec fonction Sleep
    Par Evens dans le forum C++
    Réponses: 2
    Dernier message: 28/11/2007, 14h14
  4. [Débutant] Problème de corrélation entre deux vecteurs vitesses
    Par sydneya dans le forum Signal
    Réponses: 2
    Dernier message: 29/08/2007, 08h08
  5. Problème de pause dans un clip
    Par jeanfly dans le forum Flash
    Réponses: 6
    Dernier message: 04/05/2007, 14h23

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