Merci JonathanTC, je vais essayer ça.
Une autre question (je suis intarissable, mdr).
Eclipse me dit que je peux sérialiser mes classes avec serialVersionUID. ça sert à quoi de sérialiser ?
Version imprimable
Merci JonathanTC, je vais essayer ça.
Une autre question (je suis intarissable, mdr).
Eclipse me dit que je peux sérialiser mes classes avec serialVersionUID. ça sert à quoi de sérialiser ?
pour sauvegarder un objet, vous devez rendre votre classe sérialisable à l'aide de l'interface Serializable.
Vous pouvez enregistrer directement votre objet avec ses données dans un fichier par exemple.
le serialVersionUID permet de vérifier si l'objet utiliser pour récupération est le même que celui utiliser pour l'enregistrement.
Merci !
Donc si j'ai bien compris, vu que je ne souhaite absolument rien sauvegarder en quittant le programme, je n'ai aucun intérêt à utiliser cette fonction.
Et merci aussi pour la solution pour "tuer un vieux JPanel". ça fonctionne à merveille.
Là je bosse sur une nouvelle caractéristique. Dans un de mes JPanel j'ai ajouté un bouton qui me fait des calculs et m'affiche un JLabel contenant le résultat. ça fonctionne. Mais maintenant je cherche à faire en sorte que quand j'appuie sur la touche "Entrée" j'ai le même comportement que si je cliquais sur le bouton.
Pour ce faire j'ai commencé par sortir les instructions de l'ActionListener vers une méthode à part que je pourrait appeler autant avec un ActionListener qu'avec un KeyListener.
Avec l'ActionListener ça fonctionne toujours mais pas moyen de faire fonctionner le KeyListener. J'ai trouvé pas mal de sujet identiques sur des furums mais aucune des solutions ne fonctionne pour moi. Je continue à creuser...
Pour faire simple vous devez implémenter l'interface KeyListener à la JPanel en question.
voici un exemple de code :
par contre il faudra obligatoirement donner le focus à votre Panel quand vous cliquez sur le bouton pour y accéder.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 public class PanHS6151 extends JPanel implements KeyListener{ public PanHS6151() { this.setFocusable(true); this.addKeyListener(this); } public void paintComponent (Graphics g) { g.setColor(Color.RED); g.fillRect(0, 0, this.getWidth(), this.getHeight()); } @Override public void keyTyped(KeyEvent e) {} @Override public void keyPressed(KeyEvent e) { // TODO Auto-generated method stub System.out.println(e.getKeyChar()); // <- ici vous pouvez tester la touche puis lancer votre méthode de calcule } @Override public void keyReleased(KeyEvent e) {} }
voici un exemple :
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13 boutHS6151.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent b) { choixPan = 1; panHS6151 = new PanHS6151(); panHS6151.add(boutRet); setContentPane(panHS6151); panHS6151.requestFocus(); // <- pour donner le focus au JPanel revalidate(); } });
Salut,
Eclipse ne te dit pas que tu epeux sérialiser, mais que comme la classe est sérialisable (elle implémente l'interface Serializable, ou elle étend une classe qui le fait, directement ou indirectement par l'une de ses super classes), alors il est préférable d'indiquer une version de classe.
ça ne sert pas qu'à la sauvegarde. Plus généralement, ça introduit un mécanisme de conversion vers texte ou depuis texte. Cela peut être utilisé par la sauvegarde/restauration, mais c'est également utiliser pour le transport sur le réseau, par exemple via RMI, un protocole de partage réseau d'instances d'objet.
A noter que ce mécanisme plus ou moins générique/automatique est exploité par des hackers pour injecter du code dans des systèmes distants, et que donc, Oracle envisage de supprimer cette possibilité en standard du moins.
C'est bien le souci avec un KeyListener, c'est que le composant d'écoute doit avoir le focus. Pas le panel qui le contient : dans ce cas, ça ne fonctionne que si le composant est le seul focusable dans le panel... par défaut.
Dans le cas général, on peut écouter de manière globale, en traitant le focus ou pas, en utilisant un KeyboardFocusManager par exemple. Voir exemple ici.
A noter qu'il existe une méthode fait pour gérer la notion de touche entrée frame.getRootPane().setDefaultButton(button);, mais elle pose un souci qui la rend peu praticable (lorsqu'on saisi dans un champ, il faut valider une fois la saisie dans le champ d'abord, donc appuyer deux fois sur entrée).
Merci beaucoup à tous les deux pour tous ces renseignements.Citation:
par contre il faudra obligatoirement donner le focus à votre Panel quand vous cliquez sur le bouton pour y accéder.
JonathanTC, votre code fonctionne mais partiellement. Si je clique sur le bouton qui m'amène sur mon nouveau JPanel et que là j'appuie sur Entrée, ça fonctionne (puisque désormais grâce à vous mon nouveau JPanel a le focus). Cependant, si je rempli le ou les JTextFields que j'ai mis dans ce JPanel, mon JPanel perd le focus et le KeyListener devient inopérant.
En fait il faudrait trouver un moyen de faire en sorte que quoi qu'il arrive le JPanel conserve le focus...
J'ai un bouton sur ce JPanel. Quand je clique dessus il lance une méthode qui récupère les valeurs des 2 JTextFields, fait des calculs et me renvoit le résultat dans un JLabel. En fin de méthode j'ai remis l'instruction "this.requestFocus();". Du coup, même si je clique sur le bouton je garde le focus sur le JPanel. Par contre il reste le problème des JTextFields. Quand on clique dedans pour modifier la valeur on perd forcément le focus et vu qu'aucune action n'est réalisée dans le programme, on ne peut pas renvoyer la commande "this.requestFocus();"...
C'est ce que je disais : un panel ne peut pas avoir le focus, seul un composant focusable le peut, comme un JButton ou un JTextField. S'il y a plusieurs composants focusable dans un JPanel, un seul composant peut avoir le focus et si ce n'est pas celui qui écoute le KeyListener ça ne peut pas fonctionner.
Utilise un default button comme j'ai indiqué dans mon précédent message (ou un KeyboardFocusManager pour une gestion centralisée et plus fine) :
Sinon, pour un JTextField, tu pourrais ajouter un ActionListener qui actionne ton bouton :Code:
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 public class Demo { public static void main(String[] args) { JFrame frame = new JFrame("Démo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setContentPane(new JPanel(new GridBagLayout())); JPanel panel = new JPanel(new GridBagLayout()); frame.getContentPane().add(panel); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx=GridBagConstraints.RELATIVE; gbc.fill = GridBagConstraints.BOTH; gbc.anchor = GridBagConstraints.EAST; gbc.insets = new Insets(5, 5, 5, 5); // ligne 1 gbc.gridy = 0; JTextField textfield1 = new JTextField(20); textfield1.setHorizontalAlignment(JTextField.RIGHT); panel.add(textfield1, gbc); // ligne 2 gbc.gridy = 1; JTextField textfield2 = new JTextField(20); textfield2.setHorizontalAlignment(JTextField.RIGHT); panel.add(textfield2, gbc); // ligne 3 gbc.gridy = 2; JLabel resultat = new JLabel(calculer(textfield1, textfield2), JLabel.RIGHT); resultat.setBorder(BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.LOWERED)); panel.add(resultat, gbc); JButton button = new JButton("Calculer"); panel.add(button, gbc); button.addActionListener(e->{ resultat.setText(calculer(textfield1, textfield2)); }); frame.getRootPane().setDefaultButton(button); frame.setSize(600,400); frame.setLocationRelativeTo(null); frame.setVisible(true); } private static String calculer(JTextField textfield1, JTextField textfield2) { try { int value1 = Integer.parseInt(textfield1.getText()); int value2 = Integer.parseInt(textfield2.getText()); return String.valueOf(value1+value2); } catch (NumberFormatException e) { return "?"; } } }
Mais pour un JButton, la touche d'action est "espace", pas "entrée". Tu peux régler ça avec un KeyListener.Code:
1
2
3 textfield.addActionListener(e-> { button.doClick(); });
Exemple :
Mais la première solution est plus simple !Code:
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 public class Demo2{ public static void main(String[] args) { JFrame frame = new JFrame("Démo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setContentPane(new JPanel(new GridBagLayout())); JPanel panel = new JPanel(new GridBagLayout()); frame.getContentPane().add(panel); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx=GridBagConstraints.RELATIVE; gbc.fill = GridBagConstraints.BOTH; gbc.anchor = GridBagConstraints.EAST; gbc.insets = new Insets(5, 5, 5, 5); // ligne 1 gbc.gridy = 0; JTextField textfield1 = new JTextField(20); textfield1.setHorizontalAlignment(JTextField.RIGHT); panel.add(textfield1, gbc); // ligne 2 gbc.gridy = 1; JTextField textfield2 = new JTextField(20); textfield2.setHorizontalAlignment(JTextField.RIGHT); panel.add(textfield2, gbc); // ligne 3 gbc.gridy = 2; JLabel resultat = new JLabel(calculer(textfield1, textfield2), JLabel.RIGHT); resultat.setBorder(BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.LOWERED)); panel.add(resultat, gbc); JButton button = new JButton("Calculer"); panel.add(button, gbc); textfield1.addActionListener(e-> { button.doClick(); }); textfield2.addActionListener(e-> { button.doClick(); }); button.addActionListener(e->{ resultat.setText(calculer(textfield1, textfield2)); }); button.addKeyListener(new KeyAdapter() { @Override public void keyTyped(KeyEvent e) { switch(e.getKeyChar()) { case KeyEvent.VK_ENTER: button.doClick(); } } }); frame.setSize(600,400); frame.setLocationRelativeTo(null); frame.setVisible(true); } private static String calculer(JTextField textfield1, JTextField textfield2) { try { int value1 = Integer.parseInt(textfield1.getText()); int value2 = Integer.parseInt(textfield2.getText()); return String.valueOf(value1+value2); } catch (NumberFormatException e) { return "?"; } } } ;
Désoler mais je n'avais pas imaginer votre Panel de cette façon. Effectivement si vous avez des champs de texte à l'intérieur et si à un moment donner vous devez les modifier cette solution pose un problème. Peut être que la solution de joel résoudrait votre problème.
Merci joel.drigo !
J'ai testé le code de la première solution dans eclipse, effectivement c'est pile ce qu'il me faut. Mais en l'état je ne peux pas l'intégrer à mon projet car ma JFrame et mon JPanel sont dans 2 classes distinctes. Il faut que je regarde comment adapter tout ça. (Je précise que dans mon projet j'ai 1 seule JFrame pour 11 JPanel différents. On passe d'un JPanel à l'autre en cliquant sur des boutons dédiés.)
J'ai appris pas mal de choses dans ton code. Déjà je ne savais pas qu'on pouvait appeler une méthode dans la déclaration d'un élement ("new JLabel(calculer(textfield1, textfield2)").
Et puis je suis surpris de la façon de déclarer l'ActionListener. Personnellement je suis plus habitué à ça :
Code:
1
2
3
4 button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { resultat.setText(calculer(textfield1, textfield2)); }});
On peut procéder par un accesseur éventuellement (si tu as fait une classe qui étend JPanel) pour récupérer l'instance de JButton, ou en passant l'instance de JFrame au constructeur de cette classe, ou encore en faisant une méthode de construction de JPanel.
Un autre moyen très simple est d'utilser SwingUtilities.getWindowAncestor(c). En revanche, il faut appeler cette méthode lorsque le panel est en place dans la JFrame. Ce qu'on peut faire avec un AncestorListener. L'exemple suivant fonctionne parce que j'ajoute bien l'instance de MyPanel dans son container alors que celui-ci est déjà bien dans la JFrame (en tant que content pane). Pour gérer un cas général qui fonctionne tout le temps, il faudrait regarder si le panel est bien dans une frame et enregistrer un écouteur sur chaque panneau parent, et gérer le "remove" également. Ici, j'ai simplifié pour éviter que l'exemple soit trop complexe : il suffit de se conformer à respecter l'ordre d'ajout dans ton cas.
On ne déclare pas ici, on crée une nouvelle instance, et on peut passer en paramètre, comme pour un appel de méthode aussi, une valeur littérale, une variable, ou une expression, un calcul par exemple, comme n'importe quel appel de méthode, pourvu que son retour soit compatible avec le paramètre.Code:
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 public class Demo { public static void main(String[] args) { JFrame frame = new JFrame("Démo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setContentPane(new JPanel(new GridBagLayout())); JPanel panel = new MyPanel(); frame.getContentPane().add(panel); frame.setSize(600,400); frame.setLocationRelativeTo(null); frame.setVisible(true); } private static String calculer(JTextField textfield1, JTextField textfield2) { try { int value1 = Integer.parseInt(textfield1.getText()); int value2 = Integer.parseInt(textfield2.getText()); return String.valueOf(value1+value2); } catch (NumberFormatException e) { return "?"; } } public static class MyPanel extends JPanel { public MyPanel() { super(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx=GridBagConstraints.RELATIVE; gbc.fill = GridBagConstraints.BOTH; gbc.anchor = GridBagConstraints.EAST; gbc.insets = new Insets(5, 5, 5, 5); // ligne 1 gbc.gridy = 0; JTextField textfield1 = new JTextField(20); textfield1.setHorizontalAlignment(JTextField.RIGHT); add(textfield1, gbc); // ligne 2 gbc.gridy = 1; JTextField textfield2 = new JTextField(20); textfield2.setHorizontalAlignment(JTextField.RIGHT); add(textfield2, gbc); // ligne 3 gbc.gridy = 2; JLabel resultat = new JLabel(calculer(textfield1, textfield2), JLabel.RIGHT); resultat.setBorder(BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.LOWERED)); add(resultat, gbc); JButton button = new JButton("Calculer"); add(button, gbc); button.addActionListener(e->{ resultat.setText(calculer(textfield1, textfield2)); }); addAncestorListener(new AncestorListener() { @Override public void ancestorRemoved(AncestorEvent event) { } @Override public void ancestorMoved(AncestorEvent event) { } @Override public void ancestorAdded(AncestorEvent event) { // ceci est invoqué lorsque l'instance de MyPanel est ajouté dans un contenant JFrame frame = (JFrame)SwingUtilities.getWindowAncestor(MyPanel.this); // on récupère la fenêtre qui contient l'instance de MyPanel frame.getRootPane().setDefaultButton(button); } }); } } }
La notation que j'utilise est ce qu'on appelle une expression lambda. Lorsqu'on crée une instance de classe anonyme (le new ActionListener() { de ton exemple) à partir d'une interface qui n'a qu'une seule méthode (on parle alors d'interface fonctionnelle), on peut écrire l'équivalent de la création d'instance de classe anonyme par une expression lambda.
Sa forme générale est du type (des paramètres)-> { du code }. Ici, pour ActionListener, on a une méthode actionPerformed(ActionEvent e) avec un seul paramètre. Dans ce cas, on peut omettre les parenthèses dans la partie paramètre :
On peut même omettre les accolades dans ce cas, et le point-virgule, parce qu'il y a qu'une "ligne" de code :Code:
1
2
3 button.addActionListener(e-> { resultat.setText(calculer(textfield1, textfield2)); });
C'est une façon d'écrire moins de code et de simplifier donc. Mais si tu préfères, tu peux faire comme tu fais d'habitude. Dans d'autres cas, en particulier dans l'usage de streams, ce genre de simplification apparaît beaucoup plus nécessaire.Code:button.addActionListener(e-> resultat.setText(calculer(textfield1, textfield2)));
J'ai oublié de préciser que si tu as plusieurs panels alternatifs qui doivent avoir ce comportement, comme une JFrame ne peut avoir qu'un seul bouton par défaut, il te faudra appeler setDefaultButton() sur le bon bouton à chaque fois que tu rends visible un JPanel.
J'ai adapté un de mes exemples précédents pour exemple :
Code:
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 public class DemoFrameMultiPanel { public static void main(String[] args) { JFrame frame = createFrame("Démo"); Object[][] configs = { { Color.RED, "rouge", createDefaultButton("rouge") }, { Color.YELLOW, "jaune", createDefaultButton("jaune") }, { Color.CYAN, "bleu", createDefaultButton("bleu") } }; CardLayout cardLayout = new CardLayout(); frame.getContentPane().setLayout(cardLayout); for(int i=0; i<configs.length; i++) { createPanel(frame, cardLayout, i, configs); } frame.setTitle("Panneau "+ (String)configs[0][1]); frame.getRootPane().setDefaultButton((JButton)configs[0][2]); frame.setVisible(true); } private static void createPanel(JFrame frame, CardLayout cardLayout, int i, Object[][] configs) { Color color = (Color) configs[i][0]; String id = (String) configs[i][1]; JButton defaultButton = (JButton) configs[i][2]; JPanel panel = new JPanel(new GridBagLayout()); panel.setBackground(color); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx=0; gbc.gridy=GridBagConstraints.RELATIVE; gbc.insets = new Insets(5, 5, 5, 5); Arrays.stream(configs) .filter(config-> config!=configs[i]) .map(config-> createButton(frame, cardLayout, (String) config[1], (JButton) config[2])) .forEach(button-> panel.add(button, gbc)); panel.add(defaultButton, gbc); frame.add(panel, id); } private static JButton createButton(JFrame frame, CardLayout cardLayout, String id, JButton defaultButton) { JButton button = new JButton("Afficher panneau "+id); button.addActionListener(e-> { cardLayout.show(frame.getContentPane(), id); frame.getRootPane().setDefaultButton(defaultButton); }); return button; } private static JButton createDefaultButton(String id) { JButton defaultButton = new JButton("Default"); defaultButton.addActionListener(e-> { JOptionPane.showMessageDialog(SwingUtilities.getAncestorOfClass(java.awt.Container.class, defaultButton), "Ceci est le panneau " + id); }); return defaultButton; } private static JFrame createFrame(String name) { JFrame frame = new JFrame(name); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(400, 400); frame.setLocationRelativeTo(null); return frame; } }
Je n'ai pas tout compris à ton dernier message.
Il y aura effectivement 2 Panels au total qui devront avoir ce comportement. Mais bon, si y'en a déjà un qui fonctionne ce serait un bon point.
J'ai essayé d'adapter ton code et ça ne fonctionne pas. J'ai entré ton code dans un nouveau projet dans eclipse et c'est parfait et quand je l'adapte à mon projet ça ne fonctionne plus. Je ne comprends pas pourquoi...
Au cas où, voici la classe de la JFrame :
et la classe du JPanel :Code:
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 // J'ai retiré tout le code inutile à notre cas mais il peut y avoir des reliquats oubliés. package d5; import java.awt.Color; import java.awt.Font; import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JTextField; public class FenetreMainD5 extends JFrame { private PanneauMainD5 panMainD5 = new PanneauMainD5(); Font fontbout = new Font("Courrier", Font.PLAIN, 30); private JButton boutTrack = new JButton(); public FenetreMainD5() { this.setTitle("Outils D5-Isom"); this.setSize(1700, 1000); this.setLocationRelativeTo(null); this.setDefaultCloseOperation(EXIT_ON_CLOSE); this.setResizable(false); Color mongray = new Color(100, 100, 100); this.setContentPane(panMainD5); this.setBackground(Color.DARK_GRAY); this.getContentPane().setLayout(null); boutTrack.setText("<html><center>Heure de<br>Changement de Bac</center></html>"); boutTrack.setBounds(35, 490, 295, 200); boutTrack.setBackground(mongray); boutTrack.setBorder(BorderFactory.createLineBorder(mongray)); boutTrack.setFont(fontbout); boutTrack.setFocusPainted(false); this.getContentPane().add(boutTrack); try {back = ImageIO.read(this.getClass().getResource("/d5/retour.png"));} catch (IOException e) {e.printStackTrace();} JButton boutRet = new JButton (new ImageIcon(back)); boutRet.setBounds(10, 10, 75, 50); boutRet.setBackground(mongray); boutRet.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY)); boutTrack.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent a) { PanHCB panHCB = new PanHCB(); setContentPane(panHCB); panHCB.setLayout(null); panHCB.add(boutRet); boutRet.setVisible(true); panHCB.requestFocus(); revalidate(); } }); boutRet.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent c) { setContentPane(panMainD5); boutTrack.setVisible(true); boutRet.setVisible(false); revalidate(); } }); this.setBackground(Color.DARK_GRAY); this.getContentPane().setLayout(null); } }
J'ai foiré un truc ?Code:
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 package d5; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SwingUtilities; import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; public class PanHCB extends JPanel { public PanHCB() { this.setFocusable(true); } Font ftitre = new Font("Courrier", Font.BOLD, 60); Font fontLabel1 = new Font("Courrier", Font.PLAIN, 40); Font fontLabel2 = new Font("Courrier", Font.PLAIN, 30); Font fontLabel3 = new Font("Courrier", Font.ITALIC, 40); Font font4 = new Font("Courrier", Font.BOLD, 15); private String masseString = "0", allureString = "0", newStopTime; JLabel newStopTimeLabel = new JLabel(); private int c1 = 535, c2 = 885, c3 = 1100, l1 = 350, l2 = 450; JTextField masseT = new JTextField(); JTextField allureText = new JTextField(); JLabel masse = new JLabel(); JLabel masseU = new JLabel(); JLabel allureT = new JLabel(); JLabel allureU = new JLabel(); JButton calcHCB = new JButton(); public void paintComponent (Graphics g) { g.setColor(Color.DARK_GRAY); g.fillRect(0, 0, this.getWidth(), this.getHeight()); g.setColor(Color.GRAY); g.setFont(ftitre); g.drawString("Calcul de l'heure de changement de bac", 270, 150); masse.setText("Masse pompable :"); masse.setFont(fontLabel1); masse.setForeground(Color.GRAY); masse.setBounds(c1, l1, 400, 75); masse.setVisible(true); this.add(masse); masseT.setFont(fontLabel1); masseT.setForeground(Color.GRAY); masseT.setBounds(c2, l1, 200, 75); masseT.setHorizontalAlignment(JTextField.CENTER); masseT.setText(masseString); this.add(masseT); masseU.setText("t"); masseU.setFont(fontLabel1); masseU.setForeground(Color.GRAY); masseU.setBounds(c3, l1, 25, 75); this.add(masseU); allureT.setText("Allure :"); allureT.setFont(fontLabel1); allureT.setForeground(Color.GRAY); allureT.setBounds(c1, l2, 400, 75); this.add(allureT); allureText.setFont(fontLabel1); allureText.setForeground(Color.GRAY); allureText.setBounds(c2, l2, 200, 75); allureText.setHorizontalAlignment(JTextField.CENTER); allureText.setText(allureString); this.add(allureText); allureU.setText("t/j"); allureU.setFont(fontLabel1); allureU.setForeground(Color.GRAY); allureU.setBounds(c3, l2, 35, 75); this.add(allureU); calcHCB.setText("Calcul"); calcHCB.setBounds(745, 575, 200, 50); calcHCB.setFont(fontLabel2); calcHCB.setBackground(Color.GRAY); calcHCB.setBorder(BorderFactory.createLineBorder(Color.GRAY)); calcHCB.setFocusPainted(false); this.add(calcHCB); calcHCB.addActionListener(e->{ whatToDo(); }); addAncestorListener(new AncestorListener() { @Override public void ancestorRemoved(AncestorEvent event) {} @Override public void ancestorMoved(AncestorEvent event) {} @Override public void ancestorAdded(AncestorEvent event) { JFrame FenetreMainD5 = (JFrame)SwingUtilities.getWindowAncestor(PanHCB.this); FenetreMainD5.getRootPane().setDefaultButton(calcHCB); } }); } public void whatToDo() { this.add(newStopTimeLabel); masseString = masseT.getText(); double masse = Double.parseDouble(masseString); allureString = allureText.getText(); double allure = Double.parseDouble(allureString); double allureMinute = allure/1440; double minutesToAdd = masse/allureMinute; int minutasToAdd = (int) minutesToAdd; SimpleDateFormat sdfStopTime = new SimpleDateFormat ("HH:mm"); Calendar calend = Calendar.getInstance(); calend.add(Calendar.MINUTE, minutasToAdd); Date d = calend.getTime(); newStopTime = sdfStopTime.format(d); newStopTimeLabel.setFont(fontLabel3); newStopTimeLabel.setForeground(Color.GRAY); if (masse <= 0.0 && allure > 0.0) { newStopTimeLabel.setText("Veuillez saisir une valeur correcte pour la masse pompable."); newStopTimeLabel.setBounds(320, 650, 1100, 150);} else if (masse > 0.0 && allure <= 0.0) { newStopTimeLabel.setText("Veuillez saisir une valeur correcte pour l'allure."); newStopTimeLabel.setBounds(440, 650, 900, 150);} else if (masse <= 0.0 && allure <= 0.0) { newStopTimeLabel.setText("Veuillez saisir des valeurs correctes."); newStopTimeLabel.setBounds(520, 650, 800, 150);} else { newStopTimeLabel.setText("Le changement de bac est prévu à " + newStopTime); newStopTimeLabel.setBounds(480, 650, 800, 150);} newStopTimeLabel.setVisible(true); this.requestFocus(); revalidate(); } }
ON ne peut pas avoir deux boutons par défaut en même temps. Si ces boutons sont dans un panel différent, qui ne sont pas affichés en même temps, à un moment donné le bouton d'un panel pourra être bouton par défaut. Mais le bouton par défaut va changer, au moment où le panneau qui le contient va être affiché. Si tu procèdes avec l'AncestorListener, comme tu as fait avec PanHCB, ça va fonctionner fonctionner parce que tu crées un nouveau PanHCB et tu l'ajoutes. Et donc l'AncestorLIstener va se déclencher à ce moment là.
Tu peux aussi te passer de l'AncestorListener :
- il n'y a qu'une seule JFrame
- Tu crées une nouvelle instance de panel
- tu contrôles le moment exact où tu ajoutes le panel
- tu as une classe dédiée
Donc
- tu supprimes l'AncestorListener (la partie addAncestorListener(new AncestorListener...)
- Tu ajoutes à ta classe PanHCB une méthode qui permet de récupérer le bouton :
Code:
1
2
3 public JButton getDefaultButton() { return calcHCB; }
- Puis dans le code qui "affiche" le PanHCB, tu fais le "setDefaultButton" :
Code:
1
2
3
4
5
6
7
8
9
10public void actionPerformed(ActionEvent a) { PanHCB panHCB = new PanHCB(); setContentPane(panHCB); panHCB.setLayout(null); panHCB.add(boutRet); boutRet.setVisible(true); panHCB.requestFocus(); FenetreMainD5.this.getRootPane().setDefaultButton(panHCB.getDefaultBUtton()); revalidate(); }
Tu as mis le code de construction de l'UI de PanHCB dans sa méthode paintComponent, donc il ne s'exécute pas au bon moment, et il risque même de s'exécuter plusieurs fois.
- la méthode paintComponent sert à dessiner dans le fond du JPanel quand aucunes des classes de Swing ne conviennent. Quand c'est le cas, il vaut mieux les utiliser, parce qu'elles sont optimisées. Sans parler des méthodes qui risquent de provoquer des boucles ou des dysfonctionnement parce que pas dans le bon contexte.
En tout cas, pour l'ajout de composants (JLabel, JButton...) au JPanel, les valdate (la plupart ne sont pas utile d'autant plus que tu contourne le boutot du layout manager, donc pourquoi lui demander le boulot), setVisible etc, fais ça dans le constructeur de PanHCB ;
Si tu as vraiment besoin de layouter à la main (c'est plutôt une mauvaise idée dans un contexte responsive mais bon), supprime le layout manager, sinon vire tous ces setBound, revalidate/validate/invalidate, ça va retirer beaucoup code inutile, et laisse un LayoutManager faire le job, en lui donnant les contraintes adéquates.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 public PanHCB() { this.setFocusable(true); calcHCB.setText("Calcul"); calcHCB.setBounds(745, 575, 200, 50); calcHCB.setFont(fontLabel2); calcHCB.setBackground(Color.GRAY); calcHCB.setBorder(BorderFactory.createLineBorder(Color.GRAY)); calcHCB.setFocusPainted(false); this.add(calcHCB); calcHCB.addActionListener(e -> { whatToDo(); }); /* etc */
Et par ailleurs, je ne vois rien dans ta méthode paintComponent qui justifie de redéfinir paintComponent.
ne sert à rien.Code:
1
2 g.setColor(Color.DARK_GRAY); g.fillRect(0, 0, this.getWidth(), this.getHeight());
Dans le constructeur :
Code:this.setBackground(Color.DARK_GRAY);
ne sert à rien : ça se fait tout seul avec un JLabel.Code:
1
2
3
4 g.setColor(Color.GRAY); g.setFont(ftitre); g.drawString("Calcul de l'heure de changement de bac", 270, 150);
Et enfin, au lieu de t'enquiquiner avec
ça fait la même chose :Code:
1
2
3 try {back = ImageIO.read(this.getClass().getResource("/d5/retour.png"));} catch (IOException e) {e.printStackTrace();} JButton boutRet = new JButton (new ImageIcon(back));
Plus simple, plus concis, et pas de variable back inutile...Code:JButton boutRet = new JButton(new ImageIcon(this.getClass().getResource("/d5/retour.png")));
Un énorme merci à toi !!!
ça fonctionne à la perfection. Je vais tenter de l'appliquer aux 2e JPanel mais je pense que ça va rouler.
Tu as raison, mon code n'est pas très beau (vive les newbies...). J'ai recréé ma classe PanHCB en déclarant les choses au bon endroit et au passage ça a réglé quelques petits bugs d'affichage.
En fait, au début, dans le premier JPanel que j'ai créé j'avais juste besoin d'afficher une image. Donc la méthode paintComponent était ce qu'il y avait de plus pratique. Et du coup je suis resté sur ce modèle et ce n'était pas spécialement une bonne idée.
ça, ça servait à résoudre un problème de surimpression. Quand je cliquais plusieurs fois sur le bouton calcul, les résultats se superposaient et devenaient donc illisibles. Mais en déclarant au bon endroit et en n'utilisant plus la méthode paintComponent, ce souci a disparu dont effectivement ces lignes sont inutiles.
Tu as raison aussi sur le fait que je devrais utiliser un LayoutManager au lieu de tout faire à la main. Mais à ce jour je ne maîtrise pas les LayoutManager. J'apprends le Java avec le livre "Apprenez à programmer en Java" de Cyrille Herby (que je trouve vraiment super) et la partie sur les LayoutManager n'est pas super détaillée à mon goût. Du coup j'ai opté pour le mode bourrin. Mais je reconnaîs qu'à l'avenir il faudra que je m'y colle si je veux progresser.
Encore une fois merci à vous deux @joel.drigo et @JonathanTC, vous m'avez appris beaucoup de choses et j'espère que ce topic sera utile à plein de débutants comme moi !
Je m'en vais de ce pas assainir mon code et finaliser mon projet. Je vous tiendrais au courant quand j'aurais fini, en espérant ne plus avoir de questions à vous poser. ;)
C'est le plus simple en effet pour afficher une image de fond, en particulier si elle doit s'adapter à la taille de la fenêtre, mais ça peut aussi poser certains soucis. Voir ce sujet pour en savoir plus.
Une autre solution est d'utiliser un javax.swing.OverlayLayout.
Afficher un dessin original, ou une animation (encore que ça peut être un peu compliqué à cause du multi-buffering si nécessaire). Ou afficher une texture en le fond du panel en répétant un motif nécessite de passer par une rédéfinition d'une méthode du type paintxxx, setBackground ne prenant pas du Paint en paramètre mais du Color. D'autres cas aussi comme le code que je propose ici pour montrer la grille d'un GridBagLayout à fin de déboguage. Mais en général, ce n'est pas nécessaire, surtout pour des UI de type formulaires.
Pour ce problème, il me semble bien avoir indiqué que c'était dû au recyclage du contexte graphique et qu'il suffisait d'appeler super.paintComponent(g) pour l'éviter.
Un petit retour sur ma première application.
Grâce à votre aide tout fonctionne à merveille. J'ai recréé un projet tout neuf pour corriger certaines erreurs de nommage (respect des usages java) et pour nettoyer le code. J'ai ajouté pas mal de commentaires pour m'en servir de support pour la suite.
Il me reste juste un petit point à régler.
J'ai exécuté mon programme sous Linux et je me suis aperçu que le rendu diffère. Et ce à cause des polices. Sous Windows j'ai pourtant utilisé une police "de base" (je n'en ai pas ajouté après l'installation de l'OS) mais elle n'est pas présente sous Linux. Alors je pourrais paramétrer Linux pour qu'il utilise aussi les polices Windows mais mon programme est fait pour être partagé notamment à des gens qui ne connaissent rien en informatique. Donc si je commence à indiquer des manips à faire pour profiter pleinement de mon programme, je ne vais pas avoir un grand succès, mdr.
C'est pourquoi, l'idéal serait de pouvoir embarquer la ou les polices utilisées dans mon fichier .jar exécutable. C'est possible ça ?
J'ai cherché sur le net, je n'ai rien trouvé de probant...
Quand j'aurais réglé ce point, je me pencherais sur l'utilisation des Layouts car j'ai bien compris qu'ils sont incontournables.
Encore merci à JonathanTC et joel.drigo !
Je viens de trouver une solution qui fonctionne, je la partage ici au cas où ça pourrait aider quelqu'un :
Tout d'abord on copie le fichier de la police dans le projet (moi je l'ai mis à la racine).
Ensuite, au début de la classe on ajoute :
Dans la méthode, avant toute utilisation de la police :Code:
1
2 private InputStream is = null; private Font onboardFont;
Et enfin, au moment d'utiliser la police (sur un bouton par exemple) :Code:
1
2
3
4
5 try { is = this.getClass().getResourceAsStream("/d5/ERASLGHT.TTF"); onboardFont = Font.createFont(Font.TRUETYPE_FONT, is);} catch (FontFormatException ffe) {} catch (IOException e) {}
Code:monbouton.setFont(onboardFont.deriveFont(Font.PLAIN, 27));
Voilà, j'espère que aidera...
[edit]tu as fini par trouver par toi-même...
Je le laisse, parce que plutôt que de faire setFont sur chaque bouton, l'exemple que je montre te permettra de changer la police par défaut des boutons (avec l'id "Button.font")
Si tu veux affecter tes polices directement, tu peux bien sûr faire :Code:
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 public class FontEras { public static Font ERASLIGHT = createErasLightFont(); public static void main(String[] args) { setAllUIFont(FontEras.ERASLIGHT.deriveFont(Font.PLAIN, 24)); //setUIFont("Label.font", FontEras.ERASLIGHT.deriveFont(Font.PLAIN, 24)); JFrame frame = new JFrame("Démo"); JLabel label = new JLabel("Démo"); JPanel panel = new JPanel(new GridBagLayout()); panel.add(label); frame.getContentPane().add(panel); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(300,300); frame.setLocationRelativeTo(null); frame.setVisible(true); } /** * Crée la police à partir du fichier se trouvant dans le classpath (dans le dossier d5) * @return */ public static Font createErasLightFont() { try(InputStream is = FontEras.class.getResourceAsStream("/d5/ERASLGHT.TTF")) { return Font.createFont(Font.TRUETYPE_FONT,is); } catch (FontFormatException | IOException e) { e.printStackTrace(); return null; } } /** * Set la police au type de composant d'id spécifié (par exemple Label pour les JLabel) * @param componentId * @param font */ public static void setUIFont (String componentId, Font font) { setUIFont(componentId, new FontUIResource(font)); } public static void setUIFont (String componentId, javax.swing.plaf.FontUIResource fontui) { Collections.list(UIManager.getDefaults().keys()) .stream() .filter(key-> Objects.equals(key, componentId)) .forEach(key-> { Object value = UIManager.get (key); if (value instanceof javax.swing.plaf.FontUIResource ) { UIManager.put (key, fontui); } }); } /** * Set la police pour tous les types de composant * @param componentId * @param font */ public static void setAllUIFont (Font font) { setAllUIFont(new FontUIResource(font)); } public static void setAllUIFont (javax.swing.plaf.FontUIResource fontui) { Collections.list(UIManager.getDefaults().keys()) .forEach(key-> { Object value = UIManager.get (key); if (value instanceof javax.swing.plaf.FontUIResource) { UIManager.put (key, fontui); } }); } /** * Affiche tous les types de composants */ public static void outAllFontKeys () { Collections.list(UIManager.getDefaults().keys()) .forEach(key-> { Object value = UIManager.get (key); if (value instanceof javax.swing.plaf.FontUIResource) { System.out.println(key); } }); } }
ou utiliser deriveFont, pour changer le style et/ou la tailleCode:
1
2 JLabel label = new JLabel("machintruc"); label.setFont(FontEras.ERASLIGHT);
Code:
1
2 JLabel label = new JLabel("machintruc"); label.setFont(FontEras.ERASLIGHT.deriveFont(Font.BOLD, 24));
Oui j'ai fini par trouver mais il a fallut que je combine plusieurs sujets trouvé sur des forums parce que tous ceux qui ont posé la même question que moi n'ont eu aucune réponse.
Mais bon, le principal c'est que j'ai pu synthétiser une solution ici.
Merci joel.drigo pour ton complément d'information. Je t'avoue que je vais devoir creuser ton code pour tout comprendre... ;)