Placement des composants
Chaque conteneur gère le placement de ses composants à l'aide d'un LayoutManager qui se charge des calculs de placement.
La méthode setLayout(LayoutManager) de la classe Container permet de spécifier le gestionnaire de placement utilisé.
Le placement de chaque composant dépend de sa position voulue et de ses dimensions qui sont gérées par :
- les méthodes Rectangle getBounds() et setBounds(Rectangle) pour le rectangle englobant
- les méthodes Point getLocation() et setLocation(Point) pour le coin haut-gauche
- les méthodes Dimension getSize() , setSize(Dimension) et autres méthodes concernant les tailles minimum, maximum et préférée.
Le placement dépend aussi des marges ( Insets ) placées autour du composant.
Un FlowLayout place les composants de gauche à droite puis de bas en haut.
FlowLayout
public class FlowTest {
public static void main(String[] args) { Frame f = new Frame("FlowTest");
f.setLayout(new FlowLayout());
for(int i=0;i<12;i++) f.add(new Button("But"+i));
f.setSize(new Dimension(100,100));
f.setVisible(true);
} }
BorderLayout
public class BorderTest{
public static void main(String[] args){
Frame f = new Frame("BorderTest1");
f.setLayout(new BorderLayout());
f.add(new Button("Nord"), BorderLayout.NORTH);
f.add(new Button("Sud"), BorderLayout.SOUTH);
f.add(new Button("Est"), BorderLayout.EAST);
f.add(new Button("Ouest"), BorderLayout.WEST);
f.add(new Button("Centre"), BorderLayout.CENTER);
f.setSize(new Dimension(500,500));
f.setVisible(true);
} }
Un BorderLayout gère cinq zones sur le container, on doit spécifier dans quelle
zone on ajoute les composants
GridLayout
public class GridLayoutTest extends Frame { public GridLayoutTest() {
super("GridLayoutTest");
Panel c = new Panel();
c.setLayout(new GridLayout(2,4,3,3));
c.setBackground(Color.blue);
List l = new List();
c.add(new Button("1"));
l.add("un");
l.add("deux");
l.add("trois");
c.add(l);
c.add(new Button("2"));
c.add(new Button("3"));
c.add(new Button("5"));
c.add(new Button("6"));
c.add(new Button("extra-large"));
this.add(c);
this.pack();
this.setVisible(true);
} }
Un GridLayout gère une grille de composants.
GridBagLayout
public GridBagLayoutTest(){
super("GridbagLayoutTest");
JButton button;
this.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH; c.weightx = 1.0;
button = new JButton("Bouton 1"); this.add(button, c);
button = new JButton("Bouton 2"); this.add(button, c);
button = new JButton("Bouton 3"); this.add(button, c);
button = new JButton("Bouton 4");
c.gridwidth = GridBagConstraints.REMAINDER;
this.add(button, c);
button = new JButton("Bouton 5"); this.add(button, c);
c.gridwidth = GridBagConstraints.RELATIVE;
button = new JButton("Bouton 6") ; this.add(button, c);
button = new JButton("Bouton 7");
c.gridwidth = GridBagConstraints.REMAINDER;
this.add(button, c);
button = new JButton("Bouton 8");
c.gridwidth = 1; c.gridheight = 2; c.weighty = 1.0;
this.add(button, c);
button = new JButton("Bouton 9");
c.gridwidth = GridBagConstraints.REMAINDER;
c.gridheight = 1; this.add(button, c);
button = new JButton("Bouton 10");
c.weighty = 0.0; this.add(button, c);
this.pack();
this.setVisible(true);
}
BoxLayout
public BoxLayoutTest() { super("BoxLayoutTest");
Box b1 = new Box(BoxLayout.Y_AXIS);
Button button = new Button("Button 1");
b1.add(button);
button = new Button("Button 2");
button.setPreferredSize(new Dimension(200,100));
b1.add(button);
Box b2 = new Box(BoxLayout.X_AXIS);
button = new Button("Button 3");
b2.add(button);
button = new Button("Button 4");
button.setPreferredSize(new Dimension(200, 100));
b2.add(button);
b1.add(b2);
this.add(b1);
this.pack();
this.setVisible(true);
}
Un BoxLayout place les composants en ligne verticale ou horizontale. Il permet aussi
d'ajouter des blocs invisibles de taille fixe ou variable. Un composant Box est un
conteneur géré par BoxLayout.
Autres gestionnaires de géométrie
- CardLayout : les composants sont affichés comme un tas de cartes avec seulement le composant du dessus visible
- SpringLayout : positionnement par contraintes
- ScrollPaneLayout : pour les conteneurs avec ascenceurs - GroupLayout : organisation hiérarchique des composants
Il est bien sur possible d'écrire ses propres gestionnaires de placement en implémentant l'interface LayoutManager (ou la sous-interface LayoutManager2 ).
Par défaut, un JPanel possède un FlowLayout , le panneau de contenu d'une
JFrame est par défaut un BorderLayout
Gestion des événements
L'interface doit réagir à des événements :
- action de l'utilisateur : clic souris, appui sur une touche du clavier, … - changement d'état d'un composant, …
Le pattern de réaction est du type Observer : - un composant est la source d'événement - un objet représente l'événement ( AWTEvent )
- l'objet événement est transmis à un écouteur ( EventListener )
Les écouteurs définissent le comportement via des méthodes spécifiées dans des interfaces et appelées par la source.
Tout composant peut produire des événements de divers types ( FocusEvent , ActionEvent , KeyEvent , MouseEvent , WindowEvent , ...)
Les événéments encapsulent des informations sur le type de l'événement, sa
source, le contexte (touche SHIFT, CTRL, ... appuyée, etc)
Exemple ActionListener
public class ListenerTest extends JFrame implements ActionListener{
private JButton but;
private boolean state = true;
public ListenerTest(){
super("Test ecouteur");
this.but = new JButton("Cap?");
this.but.setBackground(Color.RED);
this.but.addActionListener(this);
this.getContentPane().add(this.but);
this.setSize(200,200);
this.setVisible(true);
}
public void actionPerformed(ActionEvent ae){
this.state = ! this.state;
if(!this.state) this.but.setBackground(Color.BLUE);
else this.but.setBackground(Color.RED);
} }
Source : un bouton
Événement : clic sur le bouton ( ActionEvent )
Écouteur : la frame elle même qui implémente l'interface ActionListener
Attention : l'écouteur doit s'enregistrer comme tel auprès de la source
Ecouteurs (1/2)
Tout objet peut être écouteur, il suffit qu'il implémente l'interface correspondante et s'enregistre auprès d'une source d'événement.
Plusieurs écouteurs peuvent écouter une même source et un même objet peut écouter plusieurs sources.
Exemple : une zone texte s'écoute elle-même pour gérer les événements clavier
public class MyTextField extends JTextField implements KeyListener{
private boolean state = true;
public MyTextField(){
super(20);
this.setBackground(Color.WHITE);
this.addKeyListener(this);
}
public void keyPressed(KeyEvent e){
if(e.getKeyCode()==KeyEvent.VK_ENTER){
this.state = ! this.state;
if(!this.state) this.setBackground(Color.CYAN);
else this.setBackground(Color.WHITE);
} }
public void keyReleased(KeyEvent e){}
Ecouteurs (2/2)
Exemple : une fenêtre écoute les événements de focus sur la zone texte
public class TestFocus extends JFrame implements FocusListener{
private MyTextField mtf;
public TestFocus(){
super("Test focus");
this.mtf = new MyTextField();
this.mtf.setFocusable(true);
this.mtf.addFocusListener(this);
this.add(this.mtf,BorderLayout.NORTH);
this.add(new JButton("OK"),BorderLayout.SOUTH);
this.setSize(500,500);
this.setVisible(true);
}
public void focusGained(FocusEvent e){
this.mtf.setText("Focus");
}
public void focusLost(FocusEvent e){
this.mtf.setText("Pas Focus");
} }
Adapteurs
On peut ne pas redéfinir toutes les méthodes en utilisant un Adapter , une classe qui redéfinit les méthodes avec des corps vides. Les Adapter sont généralement spécialisés et instanciés par une classe anonyme.
public class MyTextField extends JTextField{
private boolean state = true;
public MyTextField(){
super(20);
this.setBackground(Color.WHITE);
this.addKeyListener(new KeyAdapter(){
public void keyPressed(KeyEvent e){
if(e.getKeyCode()==KeyEvent.VK_ENTER) changeState();
} });
}
private void changeState(){
this.state = ! this.state;
if(!this.state) this.setBackground(Color.CYAN);
else this.setBackground(Color.WHITE);
} }
Actions (1/2)
La classe AbstractAction implémente l'interface ActionListener et permet de définir et de décrire un comportement partagé par plusieurs composants.
public class MyAction extends AbstractAction{
public MyAction(){
super("MonAction");
this.putValue(SHORT_DESCRIPTION,"C'est mon action"); // infobulle this.putValue(MNEMONIC_KEY,KeyEvent.VK_A); // raccourci alt+A
}
public void actionPerformed(ActionEvent e){
System.out.println("blabla");
} }
...
MyAction ma = new MyAction() ; JButton b = new JButton("OK");
b.setAction(ma);
...
JMenuItem jmi = new JMenuItem("OK");
jmi.setAction(ma);
...
Actions (2/2)
On peut créer différentes actions sur un même composant et les gérer via une carte d'actions et une carte d'entrées.
public class AnnulAction extends AbstractAction{
public AnnulAction(){
super("AnnulAction");
this.putValue(MNEMONIC_KEY,KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK));
}
public void actionPerformed(ActionEvent e){
...
} }
...
monComposant.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z,ActionEvent.CTRL_MASK), "Annulation");
monComposant.getActionMap().put("Annulation",new AnnulAction());
...
Evénements et Threads
La gestion des événements se fait dans un thread spécial de la JVM : Event Dispatch Thread. Un traitement long lors d'un événement peut bloquer l'interface, et doit donc être lancé de façon asynchrone :
...
public void actionPerformed(ActionEvent e) { new Thread(new Runnable() {
grosCalculSuperLong();
SwingUtilities.invokeLater(new Runnable() { public void run() {
miseAJourdInterface();
} });
}).start();
} ...
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() { public void run() {
new MaJFrame();
} });
}