consuffisait de demander ..
Le fait est qu'entre une implémentation que je ne connais pas et quelqu'un qui avait l'air d'avoir du mal avec la synchro des threads et de l'autre coté quelqu'un comme Potochkin qui publie un article sur le problème, j'ai préféré (a tort et je m'en excuse) de croire l'expert ..
Maintenant je suis sur le coup et je te file la solution des que je l'ai trouve moi même ...
Bulbo![]()
Bizarre la demo du blog marche mais mon code pas
Bulbo![]()
Bon j'ai trouvé ce qui n'allait pas, comme le dernier type a avoir poste un commentaire sur le blog
D'ailleurs sa solution est a la lettre celle que j'ai écrit avant de voir le message
En fait pour que le inputVerifier marche, il faut que ton composant (le glasspane) ai le focus.
Le moyen le plus efficace pour ça est de redéfinir la méthode setVisible(), dans la démo c'est fait dans la MainFrame et seulement pour le betterGlassPane, m'sieur Potochkin nous a bien enduit d'erreur sur ce coup la
A ajouter au glasspane:
Testé chez moi et ça fonctionne maintenant.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9 @Override public void setVisible(boolean aFlag) { super.setVisible(aFlag); if (aFlag) { requestFocusInWindow(); } }
Bulbo![]()
Salut,
Je pense avoir trouvé une solution pour bloquer l'EDT tout en continuant ses traitement... et faire fonctionner le tout !
Je teste cela et je vous poste le code
a++
Moi j'avais fait ceci pour afficher une fenêtre modale et malgré tout lancer un traitement dans l'EDT une fois la fenêtre affichée :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12 JDialog dialog = ...; dialog.addWindowListener(new WindowAdapter() { public void windowOpened(WindowEvent e) { // forcer le repaint du contenu du dialogue pour être // certain qu'il est affiché à l'écran avant que le // traitement ne prenne la main dialog.getContentPane().repaint(); SwingUtilities.invokeLater(doRun); } }); dialog.setVisible(true);
adiGuba> Tu en as dit trop ou pas assez
Si tu ne veux pas filer un bout de code (que je m'amuse aussi) tout de suite, au moins indique un peu la méthode ou la piste que tu as suivi ..
Bulbo.impatient de tester.
Justement les dialog modales gères déjà le blocage de l'EDT
Donc je récapitule ce qui a déjà été dis :
- On utilise un GlassPane qui viendra cacher le contenu de la fenêtre afin d'afficher un message.
- On utilise des Key/MouseListeners sur ce GlassPane qui se contente de consommer les évènements, afin qu'ils ne soient pas passé au ContentPane et qu'il reste bien bloqué.
- On utilise un requestFocusInWindows() sur notre GlassPane pour donner le focus une fois qu'il est visible.
- On utilise conjointement un InputVerifier pour interdire les vérifications et ainsi interdire les changements de focus vers le ContentPane.
Maintenant pour bloquer l'EDT sans le bloquer je récupère l'EventQueue (la classe qui gère les traitements des tâches de l'EDT) via la méthode suivante :
Ce qui me permet de récupérer les tâches en attentes et de les traiter en attendant que le glasspane soit de nouveau invisible.
Code : Sélectionner tout - Visualiser dans une fenêtre à part EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
Problème : le code de traitement des tâches est situé dans une méthode protected de EventQueue dont je n'ai pas accès, et pour éviter de la recopier j'ai donc créer une classe fille...
Bref lorsque je fais setVisible(true), je boucle sur les tâches de l'EDT et je les traitent jusqu'à ce que le GlassPane soit à nouveau caché, et là je peux reprendre le cours normal du programme (et sortir du setVisible(true)). Dans setVisible() on traite le cas où l'on vient d'une autre thread en utilisant un invokeAndWait()
Enfin j'ai créer une méthode static showMessageBox() similaire à JOptionPane.showMessageDialog() mais qui utilise le GlassPane et un JOptionPane (mais sans boite de dialogue). Il serait d'ailleurs possible de redéfinir les mêmes méthodes que JOptionPane...
Voici ce que cela donne (j'ai mis la classe entière avec les imports - et j'ai essayé de mettre un max de commentaire) :
J'en ai profité pour donner un fond noir transparent au GlassPane
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252 import java.awt.AWTEvent; import java.awt.AlphaComposite; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Composite; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.InputVerifier; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingUtilities; public class ModalGlassPane extends JPanel { private static final long serialVersionUID = 1L; /** * On étend EventQueue simplement pour avoir accès à la méthode * dispatchEvent() et la rendre public (elle est protected). Cette classe va * permettre de traiter les tâches de l'EDT. */ private static class PublicEventQueue extends EventQueue { @Override public void dispatchEvent(AWTEvent event) { super.dispatchEvent(event); } } /** * Classe qui regroupe tous les listeners clavier/souris et qui se * contentent de "consommer" les évenements. * */ private static class ConsumeListener implements KeyListener, MouseListener, MouseMotionListener, MouseWheelListener { /** * Cas particulier : on emet un "beep" lors d'un clic sur la zone * transparente du glasspane */ public void mouseClicked(MouseEvent e) { e.consume(); if (e.getComponent().getComponentAt(e.getX(), e.getY()) instanceof ModalGlassPane) { Toolkit.getDefaultToolkit().beep(); } } public void keyPressed(KeyEvent e) { e.consume(); } public void keyReleased(KeyEvent e) { e.consume(); } public void keyTyped(KeyEvent e) { e.consume(); } public void mouseDragged(MouseEvent e) { e.consume(); } public void mouseMoved(MouseEvent e) { e.consume(); } public void mouseEntered(MouseEvent e) { e.consume(); } public void mouseExited(MouseEvent e) { e.consume(); } public void mousePressed(MouseEvent e) { e.consume(); } public void mouseReleased(MouseEvent e) { e.consume(); } public void mouseWheelMoved(MouseWheelEvent e) { e.consume(); } } /** * Constructeur par défaut. Construit un JPanel lié à un BorderLayout */ public ModalGlassPane() { super(new BorderLayout()); // On utilise un listener qui consommera tous les evenements // clavier/souris // Afin d'éviter qu'ils ne soient transmis au JRootPane et donc au // contenu de la fenêtre ConsumeListener consume = new ConsumeListener(); addKeyListener(consume); addMouseListener(consume); addMouseMotionListener(consume); addMouseWheelListener(consume); // On utilise un InputVerifier pour empêcher le focus de retourner sur // le ContentPane setInputVerifier(new InputVerifier() { @Override public boolean verify(JComponent input) { return !isVisible(); } }); // On utilise un fond noir sans opacité // (paintComponent() est redéfini pour afficher une transparence) setBackground(Color.BLACK); setOpaque(false); } /** * Redéfinition de paintComponent() qui affiche un filtre noir transparent * sur le ContentPane. */ @Override protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.setColor(getBackground()); Composite composite = g2d.getComposite(); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f)); g2d.fillRect(0, 0, getWidth(), getHeight()); g2d.setComposite(composite); } /** * Redéfinition de setVisible() afin de la rendre modale (l'appel à * setVisible(true) est bloquant jusqu'à ce que le composant soit à nouveau * rendu invisible). Cette méthode peut être appellé de n'importe quel * thread ;) */ @Override public void setVisible(final boolean aFlag) { // Si on est bien dans l'EDT : if (SwingUtilities.isEventDispatchThread()) { // On évite des traitements inutile si l'état ne change pas : if (aFlag != isVisible()) { // On modifie l'état super.setVisible(aFlag); // Si le nouvel état consiste à afficher la fenêtre if (aFlag) { // On demande le focus (pour éviter que le ContentPane ne le // conserve) requestFocusInWindow(); // On récupère l'EventQueue, c'est à dire la queue des // traitements de l'EDT : EventQueue eventQueue = Toolkit.getDefaultToolkit() .getSystemEventQueue(); // On se crée notre propre EventQueue : PublicEventQueue publicQueue = new PublicEventQueue(); // Tant que le composant est visible : while (isVisible()) { try { // On récupère les AWTEvent un à un pour les traiter // avec dispatchEvent publicQueue .dispatchEvent(eventQueue.getNextEvent()); } catch (Throwable t) { // Si un problème survient on la remonte dans le // handler : Thread current = Thread.currentThread(); current.getUncaughtExceptionHandler() .uncaughtException(current, t); } } } } } else { // Sinon (on n'est pas dans l'EDT) // On utilise invokeAndWait() pour passer dans l'EDT et attendre la // fin : try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { // Et on rappelle setVisible() depuis l'EDT ! setVisible(aFlag); } }); } catch (Exception e) { throw new RuntimeException(e); } } } /** * Affiche une message de la même manière que * JOptionPane.showMessageDialog(), mais via le GlassPane de la fenêtre ;) */ public static void showMessageBox(JFrame frame, Object message) { // On crée le glasspane modal final ModalGlassPane glassPane = new ModalGlassPane(); // On crée l'OptionPane qui correspondra au message à afficher : JOptionPane optionPane = new JOptionPane(message, JOptionPane.INFORMATION_MESSAGE, JOptionPane.OK_CANCEL_OPTION); // On surveille la valeur de l'attribut "value" // Lorsqu'il est modifié cela signifie que l'on a cliqué sur un des boutons, // et donc on peut cacher le GlassPane (ce qui nous fera continuer le programme) optionPane.addPropertyChangeListener("value", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if (evt.getNewValue() != JOptionPane.UNINITIALIZED_VALUE) { glassPane.setVisible(false); } } }); // On ajoute le panel en bas de la fenêtre : glassPane.add(optionPane, BorderLayout.SOUTH); // On sauvegarde l'ancien GlassPane et on le remplace par le ModalGlassPane : Component oldGlassPane = frame.getGlassPane(); frame.setGlassPane(glassPane); // On affiche le GlassPane (bloque jusqu'à ce qu'il soit à nouveau invisible) : glassPane.setVisible(true); // Enfin on restaure le GlassPane frame.setGlassPane(oldGlassPane); } }
Je n'ai remarqué qu'un seul problème pour le moment : l'InputVerifier marche trop bien et empêche le changement de focus même à l'intérieur du GlassPanemais en se documentant sur les mécanismes du Focus de Swing je pense qu'il doit y avoir un solution...
Voici un programme de test :
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 public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { final JFrame frame = new JFrame("Test ModalGlassPane"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(400, 200); frame.setLocationRelativeTo(null); // On y ajoute un panel avec un JTextField et un bouton : JPanel panel = new JPanel(); final JTextArea message = new JTextArea("Hello World !"); JButton goButton = new JButton("Go !"); goButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ModalGlassPane.showMessageBox(frame, message.getText()); } }); JScrollPane scrollPane = new JScrollPane(message); scrollPane.setPreferredSize(new Dimension(260,80)); panel.add(scrollPane); panel.add(goButton); frame.getContentPane().add(panel); // Ainsi qu'une JMenuBar JMenuBar menuBar = new JMenuBar(); JMenu file = new JMenu("Fichier"); file.add(new JMenuItem("Nouveau")); file.add(new JMenuItem("Ouvrir...")); file.add(new JMenuItem("Enregistrer")); file.add(new JMenuItem("Enregistrer sous...")); file.addSeparator(); file.add(new JMenuItem("Quitter")); JMenu edit = new JMenu("Edition"); edit.add(new JMenuItem("Couper")); edit.add(new JMenuItem("Copier")); edit.add(new JMenuItem("Coller")); menuBar.add(file); menuBar.add(edit); frame.setJMenuBar(menuBar); frame.setVisible(true); } }); }Lorsqu'on clique sur le bouton, le message du JTextArea est affiché via le GlassPane. Le menu est simplement là pour des tests mais il ne comporte aucun traitement
J'ai mis les screenshot en pièces jointes...
Ayé vous pouvez tester et me remonter les problèmes
a++
PS : Bulbo tu n'es pas très patient ce n'est pas bien![]()
terrible...mais...entre guillemet..j'ai presque reussi a le faire ca....je dis presque parce que....ca revient grosso-modo au meme vu que ma méthode showMessage ne renvoie rien...un peu comme la tienne...on n'a pas le resultat du clic sur le OptionPane...
Ici:
Tu sais que l'utilisateur a fait un choix, a toi de le recuperer et tu peux le retourner a l'utilisateur.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 // On affiche le GlassPane (bloque jusqu'à ce qu'il soit à nouveau invisible) : glassPane.setVisible(true); // Enfin on restaure le GlassPane frame.setGlassPane(oldGlassPane); // ICI ICI ICI
Bulbo![]()
En effet showMessageBox() ne renvoi rien mais bloque bien le thread courant.
Tu peux essayer avec la méthode suivante qui pose une question de type YES_NO_CANCEL :
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 /** * Affiche une message de la même manière que * JOptionPane.showConfirmDialog(), mais via le GlassPane de la fenêtre ;) */ public static int showConfirmBox(JFrame frame, Object message) { // On crée le glasspane modal final ModalGlassPane glassPane = new ModalGlassPane(); // On crée l'OptionPane qui correspondra au message à afficher : JOptionPane optionPane = new JOptionPane(message, JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_CANCEL_OPTION); // On surveille la valeur de l'attribut "value" // Lorsqu'il est modifié cela signifie que l'on a clicué sur un des // boutons, // et donc on peut cacher le GlassPane (ce qui nous fera continuer le // programme) optionPane.addPropertyChangeListener("value", new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if (evt.getNewValue() != JOptionPane.UNINITIALIZED_VALUE) { glassPane.setVisible(false); } } }); // On ajoute le panel en bas de la fenêtre : glassPane.add(optionPane, BorderLayout.SOUTH); // On sauvegarde l'ancien GlassPane et on le remplace par le // ModalGlassPane : Component oldGlassPane = frame.getGlassPane(); frame.setGlassPane(glassPane); // On affiche le GlassPane (bloque jusqu'à ce qu'il soit à nouveau // invisible) : glassPane.setVisible(true); // Enfin on restaure le GlassPane frame.setGlassPane(oldGlassPane); Object selectedValue = optionPane.getValue(); if (selectedValue instanceof Integer) { return ((Integer) selectedValue).intValue(); } return JOptionPane.CLOSED_OPTION; }Comme je l'ai dit il est possible de refaire toutes les méthodes du JOptionPane... je pourrais le faire mais c'est assez long et pas super-intéressant et là c'est vendredi donc ca attendra
Sinon j'ai trouvé pour le problème de l'InputVerifier : en fait cela empêche carrément le déplacement du focus ce qui n'est pas vraiment ce que l'on souhaite.
Il suffit d'utiliser à la place le code suivant dans le constructeur du GlassPane :
Cela indique que les focus se limiteront aux composants enfant du GlassPane et ne remonteront pas aux composants parents
Code : Sélectionner tout - Visualiser dans une fenêtre à part setFocusCycleRoot(true);Le focus marche reste sur le GlassPane !
a++
je mets résolu et je dis merci....ou pas ?
merki les gars....j'ai pas encore mis en place, mais en tout cas j'ai tous les éléments pour le faire...merci bcp
Partager