OVH Cloud OVH Cloud

Blocage d'affichage - Swing

8 réponses
Avatar
Eliope
Bonjour,

j'ai programm=E9 une application relativement simple qui ouvre un
fichier image et l'affiche dans une fen=EAtre. Comme l'image peut =EAtre
assez lourde, j'affiche simultan=E9ment une autre fen=EAtre qui contient
une zone de texte et une barre de progression. Au fur et =E0 mesure que
l'image se charge, j'incr=E9mente la valeur de la barre de progression,
et de temps =E0 autres j'ajoute du texte.

Mon probl=E8me, c'est que cette seconde fen=EAtre reste enti=E8rement
grise ! Tant qu'il n'y a pas certains =E9v=E9n=E9ments, rien ne s'affiche
si ce n'est la barre de titre. Les seul =E9v=E9n=E9ments que j'ai trouv=E9
qui effectuent cet affichage sont la cr=E9ation et l'affichage d'un
objet JOptionPane ou la fin du chargement de l'image. J'ai tent=E9 les
redimensionnements (manuels ou par appel de fonction), les r=E9ductions,
les paint, les pack, les repaint, les update, etc. Rien n'y fait !

Pire: le chargement de l'image est lanc=E9 par l'appel d'une unique
fonction =E0 partir d'un menu. Mais si cette fonction est appel=E9e
directement dans le main, alors l'affichage se fait sans probl=E8me au
fur et =E0 mesure du chargement !

Est-ce que ce probl=E8me dit quelque chose =E0 quelqu'un ? Je n'ai rien
trouv=E9 dans les archives en tous cas (mais j'ai toujours =E9t=E9 nul
pour les recherches :) )

D'avance merci,
Simon

8 réponses

Avatar
Hervé AGNOUX
A ma grande honte je n'ai pas réussi à retrouver les bouts de code qui vont
bien, mais je peux te donner les principes généraux.

Avec swing il y a un seul thread qui s'occupe de tous les affichages. Il ne
peut evidemment n'afficher qu'une seule chose à la fois. Donc, pendant
qu'il affiche ton image, il ne peut pas mettre à jour ta barre de
progression.

La solution est de créer un thread pour charger ton image, et de réveiller
périodiquement le thread swing pour mettre à jour la barre de progression.

Facile à dire... ce n'est pas très compliqué, mais quand on a un exemple,
c'est mieux... désolé pour l'exemple :-)


Eliope wrote:

Bonjour,

j'ai programmé une application relativement simple qui ouvre un
[...]


--
Hervé AGNOUX
http://www.diaam-informatique.com

Avatar
Eliope
Je ne suis pas certain que ce soit le problème... Je m'explique:
j'avais oublié de le préciser (honte sur moi) mais l'image ne
s'affiche que suite à un redimensionnement, une réduction, ou
l'affichage d'un JOptionPane... SAUF si la fonction de chargement n'a
pas été appelée par l'intermédiaire d'un menu !

Si c'est un problème de thread, alors pourquoi tout se passe-t-il
correctement quand je n'utilise pas les menus ? N'y a-t-il pas plutot
"quelque chose" qui est désactivé lors de l'utilisation d'un menu et
qu'il faudrait réactiver manuellement ?

Simon
Avatar
Hervé AGNOUX
Eliope wrote:

Je ne suis pas certain que ce soit le problème... Je m'explique:
j'avais oublié de le préciser (honte sur moi) mais l'image ne
s'affiche que suite à un redimensionnement, une réduction, ou
l'affichage d'un JOptionPane... SAUF si la fonction de chargement n'a
pas été appelée par l'intermédiaire d'un menu !

Si c'est un problème de thread, alors pourquoi tout se passe-t-il
correctement quand je n'utilise pas les menus ? N'y a-t-il pas plutot
"quelque chose" qui est désactivé lors de l'utilisation d'un menu et
qu'il faudrait réactiver manuellement ?



Il faudrait avoir plus de détails sur l'appli pour te répondre... J'ai un
peu de mal à comprendre ce qui se passe.

Lors de réduction d'image, de redimensionnement, il s'active habituellement
un simple repaint. Un menu, par contre, déclenche toute une série
d'actions. Du coup il n'est pas opportun de comparer les performances de
l'un et de l'autre.

En tous cas, rien de particulier n'est activé, ni désactivé, lors de
l'utilisation d'un menu : ce qui se passe dépend entièrement de ton code.
Ce code se déroule dans le thread de swing, mais c'est exactement la même
chose lors de redimenssionnement, etc.

Cordialement.


--
Hervé AGNOUX
http://www.diaam-informatique.com

Avatar
Eliope
Il faudrait avoir plus de détails sur l'appli pour te répondre...
J'ai un

peu de mal à comprendre ce qui se passe.


Je vous aurais bien copié le code sur cette page, mais il est un peu
long, à cause des codes de chargement de l'image et autre qui n'ont
aucun intérêt. Je vous ai donc mis les différents fichiers sur
Internet si vous voulez tester:
http://eliope.nerim.net/java.html

Si vous vous intéressez uniquement au code pour voir ce que j'ai mal
fait, voici les deux classes qui, je crois, sont incriminées:
- tout d'abord, la classe principale, que j'ai réduite à son strict
minimum rien que pour vous (c'est pas mignon ?):
######
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import javax.imageio.stream.*;

public class Mini extends JFrame implements ActionListener
{
private JMenuBar menuBar;
private JMenu menu;
private JMenuItem menuItem;

Mini()
{
super("A marche pô");
createMenuComponents();
setActions();
bindMenuComponents();
setJMenuBar(menuBar);
}

public static void main(String[] args)
{
Mini mainFrame = new Mini();
//mainFrame.setBounds(0,0,size.width,size.height);
mainFrame.setBounds(0,0,300,300);
mainFrame.setVisible(true);
mainFrame.loadImage(); //là, ça marche
}

private void createMenuComponents()
{
menuBar = new JMenuBar();
menu = new JMenu("Menu");
menuItem = new JMenuItem("Charger");
}

private void setActions()
{
menuItem.setActionCommand("load");
menuItem.addActionListener(this);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

private void bindMenuComponents()
{
menu.add(menuItem);
menuBar.add(menu);
}

public void actionPerformed(ActionEvent event)
{
String s = event.getActionCommand();

if(s.equals("load")) loadImage();
}

private void loadImage()
{
JFileChooser fc = new JFileChooser();
fc.setFileFilter(new BSQFileFilter());
String curDir = System.getProperty("user.dir");
fc.setCurrentDirectory(new File(curDir));

if(fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
/* Fichier correctement sélectionné */
printImage(fc.getSelectedFile());
}
}

private void printImage(File f)
{
BSQReader br = null;

try {
br = new BSQReader(f);
} catch(IOException e){
e.printStackTrace();
}

JOptionPane.showMessageDialog(this, "Quand j'affiche ça, tout d'un
coup, ça marche !");
/* Le reste fonctionne bien je vous le mets pas */
}
}
######

- puis vient la fenêtre qui doit afficher la barre de progression:
######
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ProgressIndicator
extends JPanel
{
private JProgressBar progressBar;
private JTextArea output;
private JFrame frame;
private JPanel panel;

ProgressIndicator(int max, int cur)
{
super(new BorderLayout());

progressBar = new JProgressBar(0, max);
progressBar.setValue(cur);
progressBar.setStringPainted(true);

output = new JTextArea(9,25);
output.setMargin(new Insets(5,5,5,5));
output.setEditable(false);
output.setCursor(null);

panel = new JPanel();
panel.add(progressBar);
add(panel, BorderLayout.PAGE_START);
add(new JScrollPane(output), BorderLayout.CENTER);
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
}

public void show(String str)
{
frame = new JFrame(str);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

setOpaque(true);
frame.setContentPane(this);

frame.setBounds(400,400,400,400);
frame.pack();
frame.setVisible(true);
}

ProgressIndicator(int max){
this(max, 0);
}

ProgressIndicator(){
this(100,0);
}

public void setMax(int max){ progressBar.setMaximum(max); }
public void reset(){ progressBar.setValue(0); }
public double getPercent(){ return
100*progressBar.getPercentComplete(); }
public boolean isFinished(){ return (getPercent() == 100.); }
public int getMax(){ return progressBar.getMaximum(); }
public int getProgress(){ return progressBar.getValue(); }

public void inc()
{
int percent = (int)getPercent();
progressBar.setValue(progressBar.getValue()+1);
if(percent != (int)getPercent()){
/* Vous pouvez admirer ci-dessous qqes uns des trucs que j'ai
essayé et qui se sont révélés inefficaces */
//frame.repaint();
//panel.revalidate();
//frame.update();
//setVisible(true);
//frame.setVisible(true);
}
}

public void print(String str)
{
output.append(str + "n");
output.setCaretPosition(output.getDocument().getLength());
System.out.println(str);
}

public void paintComponents(Graphics g) {
paint(this.getGraphics());
}

public void update(Graphics g) {
paint(g);
}

}
######

Les deux autres classes que j'ai mis sur le net ne font rien de
spécial: il y a un FileFilter, dont je ne pense pas qu'il soit à
l'origine du problème, et une classe qui charge l'image et fait des
appels à la méthode de ProgressIndicator qui incrémente la valeur de
la barre de progression...

Le premier qui me dit que chez lui ça marche, je lui achète sa VM :)

Simon

Avatar
Hervé AGNOUX
Moi cela me parait bien être une histoire de thread swing. Je pense que
c'est ta méthode printImage que tu devrais déporter dans un thread à part,
je suppose que cette méthode est celle qui charge l'image. Il faudra en
plus que tu règles les histoires de synchro... Bonne chance.

--
Hervé AGNOUX
http://www.diaam-informatique.com
Avatar
Eliope
Moi cela me parait bien être une histoire de thread swing.


Assailli par le doute, j'ai reéssayé de ne lancer la fonction de
chargement de l'image que dans un Thread. Ca n'a rien changé... Je me
suis dit "bon ben tant pis, t'es maudit !".

Un peu plus tard, j'ai voulu voir si la façon de faire classique pour
les actions attribués au bouton changeait quelque chose. J'ai donc
modifié mes "bouton.addActionListener(this);
bouton.setActionCommand("bouton");" suivis par une méthode unique
actionPerformed(Event e) et de tests sur la chaine dans e et je les ai
transformé en une série de addActionListener(new ActionListener
{...}).
A ma grande surprise, ça a fonctionné ! Beaucoup plus lentement que
quand la fonction de chargement est appelée dans le main, mais ça a
marché... Et puis je me suis rendu compte que je n'avais pas viré le
Thread... je l'ai enlevé et... ça marchait plus !

Il y avait donc deux choses à changer dans mon code: il fallait bel et
bien des Threads, et en plus il ne fallait pas que la fenêtre
implémente elle-même ActionListener, il en faut un par bouton ou menu
!

Enfin, en tous cas maintenant ça marche, merci beaucoup de votre aide
!

Simon

Avatar
Guillaume
Bonjour à tous
J'ai eu récemment le même problème il y a quelque temps et j'ai réussi à
résoudre le problème facilement et sans threads. Alors je sais pas si cela
va t'aider, mais je me demandais s'il était possible
de faire une action en même temps qu'une autre et ce à intervalle régulier.
Première solution évidente les threads mais on se prend la tête rapidement
surtout si l'usage des threads n'est pas justifié (comme pour l'utilisation
d'une barre de progression).
Le mieux (à mon avis) c'est d'utiliser un timer qui va générer un événement
(ActionEvent) de manière régulière et de définir la classe ActionPerformed :

timer = new Timer(100, new ActionListener() {
public void actionPerformed(ActionEvent e) {
// ton code
}
});

Pour le problème de ta fenêtre grise, utilises-tu un Layout particulier ?
Si tu utilises la JProgressBar et que tu la mets direct sur la fenêtre sans
passer par un JPanel (en mettant par exemple
TaFenetre.getContentPane().add(maBarre); la barre de progression va prendre
toute la fenêtre et donner l'impression que la fenêtre est vide. Essaye de
voir si tu obtiens le % de progression en mettant l'instruction suivante
maBarre.setStringPainted(true);
Avatar
Eliope
Le mieux (à mon avis) c'est d'utiliser un timer qui va générer un
événement

(ActionEvent) de manière régulière


Oui, enfin ça revient un peu à réinventer le thread, ça, non ?

Pour le problème de ta fenêtre grise, utilises-tu un Layout
particulier ?

Un simple BorderLayout

Si tu utilises la JProgressBar et que tu la mets direct sur la
fenêtre

Non non, elle est gentiment collée sur un JPanel lequel est placé
dans le contentPane de la fenêtre.