Interfaces graphiques
avec Swing AWT et Swing * AWT (Abstract Window Toolkit) * Package "java.awt" * Figé depuis 1.1 * Structure relativement simple * Swing * Package "javax.swing" * Toujours en évolution * Structure plus complexe qu'AWT * Surcouche d'AWT (héritage des composants AWT) Types de composants graphiques * Composants de haut-niveau * JFrame: fenêtre * JDialog: boîte de dialogue * JApplet: "applet" (application embarquée dans HTML) * Composants de niveau intermédiaire * Destinés à recevoir d'autres composants * JPanel: "panneau", zone où placer des composants * JScrollPane: zone avec ascenseur * JSplitPane: zone partagée en deux * ... * Composants élémentaires * JButton: bouton * JLabel: texte * JComboBox: menu déroulant * ... Composants de niveau intermédiaire Composants élémentaires Classe "JComponent" * Composants intermédiaire et bas niveaux héritent de "JComponent" * Tout composant Swing * Est susceptible d'en contenir d'autres * Possède les propriétés d'un composant conteneur AWT * Documentation * Fournie par Sun (dans Google: "java api 1.5") * Consulter les classes: Component, Container, JComponent Ouverture d'une fenêtre * Exemple import javax.swing.JFrame; import java.awt.Dimension; public class Fenetre extends JFrame { public Fenetre(String titre,int x,int y, int largeur,int hauteur) { setTitle(titre); setPreferredSize(new Dimension(largeur,hauteur)); setLocation(new java.awt.Point(x,y)); pack(); } public static void main(String args[]) { JFrame fenetre = new Fenetre("Nawouak",100,100,200,200); fenetre.setVisible(true); } } * "pack" dimensionne la fenêtre * En fonction de sa taille préférée * Et de la taille et du placement de chaque composant qu'elle contient Ajout de composants * Les composants de haut-niveau ont un conteneur principal * Container cp = this.getContentPane(); * Exemple public Fenetre(...) { ... Container cp = getContentPane(); JLabel label = new JLabel("Bonjour !"); JButton bouton = new JButton("Cliquer ici"); cp.add(label,BorderLayout.NORTH); cp.add(bouton,BorderLayout.SOUTH); pack(); } * Placement des composants * Les positions précises ne sont pas données * Seulement des indications: "NORTH" et "SOUTH" * Un gestionnaire de placement détermine les positions précises Gestionnaires de placement (1/2) * L'utilisateur peut changer la taille d'une fenêtre ==> les composants doivent être repositionnés * A la programmation, éviter un ordre absolu * Seules des indications sont fournies * Sur la taille et le positionnement des composants * Chaque conteneur possède un gestionnaire de placement * Aussi appelé "layout manager" * Objet chargé de repositionner les composants * En fonction de règles propres * En tentant de satisfaire les indications fournies * Plusieurs types de gestionnaires de placement * BorderLayout: placement en bordure * FlowLayout: placement en flot * GridLayout: placement en grille * BoxLayout: placement en pile * ... * Combinaison de gestionnaires ==> imbrication de conteneurs Gestionnaires de placement (2/2) * Critères de placement d'un gestionnaire * Algorithme de placement propre au gestionnaire * Indications de positionnement des composants * Contraintes de taille du conteneur * Contraintes de taille des composants * Expression des contraintes de taille * Taille minimale: composant.setMinimumSize(dimensions); * Taille maximale: composant.setMaximumSize(dimensions); * Taille préférée: composant.setPreferredSize(dimensions); * Dimensions modélisées par la classe "java.awt.Dimension" * Attributs: width, height * Constructeur: Dimension(largeur,hauteur) * Définir le gestionnaire * conteneur.setLayout(new classe_gestionnaire(paramètres)); * Placement des composants * Automatique au redimensionnement de la fenêtre * En appelant la méthode "pack" de la fenêtre Placement en bordure (1/2) * Gestionnaire: BorderLayout * Permet de placer au maximum 5 composants * 4 en bordure (nord, sud, est, ouest) * 1 au centre * Tente de respecter * La hauteur préférée du nord et du sud * La largeur préférée de l'est et de l'ouest * Le centre occupe le reste * Gestionnaire par défaut de "JFrame" Placement en bordure (2/2) * Exemple cp.setLayout(new BorderLayout()); JButton bouton1 = new JButton("B1"); ... JButton bouton5 = new JButton("B5"); bouton1.setPreferredSize(new Dimension(100,100)); cp.add(bouton1,BorderLayout.NORTH); cp.add(bouton2,BorderLayout.SOUTH); cp.add(bouton3,BorderLayout.EAST); cp.add(bouton4,BorderLayout.WEST); cp.add(bouton5,BorderLayout.CENTER); pack(); Placement en flot (1/2) * Gestionnaire: FlowLayout * Permet un placement comme dans un texte * Selon l'ordre d'ajout * De gauche à droite * Puis, de haut en bas * Alignement précisé à la construction * FlowLayout.LEFT * FlowLayout.RIGHT * FlowLayout.CENTER * Composants affichés à leur taille préférée * Gestionnaire par défaut de "JPanel" et "JApplet" Placement en flot (2/2) * Exemple cp.setLayout(new FlowLayout(FlowLayout.RIGHT)); JButton bouton1 = new JButton("B1"); ... JButton bouton4 = new JButton("B4"); bouton1.setPreferredSize(new Dimension(80,30)); bouton2.setPreferredSize(new Dimension(80,40)); bouton3.setPreferredSize(new Dimension(80,30)); bouton4.setPreferredSize(new Dimension(80,30)); cp.add(bouton1); cp.add(bouton2); cp.add(bouton3); cp.add(bouton4); pack(); Placement en grille (1/2) * Gestionnaire: GridLayout * Permet un placement sur une grille * Selon l'ordre d'ajout * De gauche à droite * Puis, de haut en bas * Nombre de lignes ou de colonnes fixé * Indications fournies au constructeur * new GridLayout(lignes,colonnes) * lignes > 0 ==> colonnes ignoré * lignes = 0 ==> lignes ignoré * La dimension ignorée est adaptée automatiquement * Les composants ont tous la même taille * Ils occupent toute la place de leur cellule dans la grille Placement en grille (2/2) * Exemple cp.setLayout(new GridLayout(3,3)); JButton bouton1 = new JButton("B1"); ... JButton bouton4 = new JButton("B4"); bouton1.setPreferredSize(new Dimension(80,10)); bouton2.setPreferredSize(new Dimension(80,10)); bouton3.setPreferredSize(new Dimension(80,10)); bouton4.setPreferredSize(new Dimension(80,10)); cp.add(bouton1); cp.add(bouton2); cp.add(bouton3); cp.add(bouton4); pack(); Placement en pile (1/2) * Gestionnaire: BoxLayout * Permet d'empiler des composants * Soit sur une ligne: BoxLayout.Y_AXIS * Soit sur une colonne: BoxLayout.X_AXIS * Direction d'empilement fournie au constructeur * new BoxLayout(conteneur,direction) * Attention: conteneur fourni à la construction * Alignement des composants * Fixé pour chaque composant: setAlignmentX * Doit être le même pour tous * Composants affichés à leur taille maximale * Gestionnaire par défaut de "JToolBar" Placement en pile (2/2) * Exemple cp.setLayout(new BoxLayout(cp,BoxLayout.Y_AXIS)); JButton bouton1 = new JButton("B1"); JButton bouton2 = new JButton("B2"); JButton bouton3 = new JButton("B3"); bouton1.setMaximumSize(new Dimension(50,20)); bouton2.setMaximumSize(new Dimension(70,20)); bouton3.setMaximumSize(new Dimension(60,20)); bouton1.setAlignmentX(Component.RIGHT_ALIGNMENT); bouton2.setAlignmentX(Component.RIGHT_ALIGNMENT); bouton3.setAlignmentX(Component.RIGHT_ALIGNMENT); cp.add(bouton1); cp.add(bouton2); cp.add(bouton3); pack(); Les événements * Evénements élémentaires * Souris * Appui d'un bouton * Déplacement du curseur * Clavier * Appui d'une touche * Saisie d'un caractère * Evénements avancés * Actions * Sélection d'un item de menu * Clic sur un composant bouton * Evénements système * Fermeture fenêtre * Redimensionnement composant Principe de l'écouteur * Capter un événement ==> observer, "écouter" * Objet chargé d'écouter: "écouteur" (ou "listener") * Ecouter ==> s'enregistrer auprès de l'objet surveillé * Evénement e déclenché sur l'objet ==> tous les écouteurs informés * Informations sur l'événement encapsulées dans un objet o * Objet o transmis à tous les objets écouteurs * Méthode e() appelée pour tous les écouteurs * Objet o passé en paramètre * Les écouteurs doivent donc respecter une interface commune Interface "Listener" * A chaque type d'événement = une interface "Listener" * MouseListener, KeyListener, WindowListener... * Définition d'une classe qui implémente l'interface class MonEcouteur implements MouseListener { void mouseClicked(MouseEvent e) { ... } void mouseEntered(MouseEvent e) { ... } void mouseExited(MouseEvent e) { ... } void mousePressed(MouseEvent e) { ... } void mouseReleased(MouseEvent e) { ... } } * L'écouteur appartient à cette classe * MouseListener ecouteur = new MonEcouteur(); * Enregistrement de l'écouteur auprès du composant * composant.addMouseListener(ecouteur); * Inconvénient: toutes les méthodes doivent être redéfinies Classe "Adapter" (1/2) Classe "Adapter" (2/2) * Chaque interface "Listener" ==> une classe "Adapter" (ou adapteur) * Interface "MouseListener" ==> classe "MouseAdapter" * Classe adapteur = classe vide implémentant l'interface associée * class MouseAdapter implements MouseListener * Toutes les méthodes sont vides * Il suffit de dériver la classe adapteur * Redéfinition des méthodes nécessaires uniquement * class MonEcouteur extends MouseAdapter { public void mousePressed(MouseEvent e) { ... } } * Conseil: déclarer l'adapteur en classe "interne" * A l'intérieur de la classe concernée * Il accède ainsi aux membres cachés de la classe * Si usage unique ==> classe "anonyme" Classes imbriquées (ou "nested classes") * Classe imbriquée = classe déclarée dans une autre class A { class B { ... } ... } * Accès à la classe imbriquée B par la classe englobante A * Dans la classe A, B est accessible directement * A l'extérieur, il faut écrire "A.B" * Mêmes règles de visibilité que les attributs et les méthodes * Relation d'amitié forte * A voit les membres privés de B * B voit les membres privés de A * Deux types de classes imbriquées en Java * Classe imbriquée statique ("static nested class") * Par abus de langage, "nested class" peut être employé * C++ ne fournit que ce type d'imbrication * Classe interne ("inner class") Classe imbriquée statique (1/2) * Utilisation du mot-clé "static" class A { static class B { ... } ... } * Classe "normale" placée dans une autre classe * Les différences * Classe B dans l'espace de nommage de A * Amitié forte entre A et B * Les instances de A et B n'ont aucun lien ==> pas d'agrégation * Accès réciproque aux membres d'instance impossible * Accès réciproque aux membres de classe naturellement possible Classe imbriquée statique (2/2) public class ClasseA { private int a; private static int b; public static class ClasseB { private int x; private static int y; public void f() { System.out.println(a); } ==> interdit, aucune instance public void g() { System.out.println(b); } ==> ok, membre de classe public void h() { ClasseA m = new ClasseA(); System.out.println(m.a); ==> ok, passage par une instance + relation d'amitié System.out.println(m.b); ==> ok, membre de classe + relation d'amitié } } public static void test() { ClasseB e = new ClasseB(); System.out.println(e.x); ==> ok, passage par une instance + relation d'amitié System.out.println(e.y); ==> ok, membre de classe + relation d'amitié } } Classe interne (1/3) * Sans mot-clé "static" class A { class B { ... } } * Caractéristiques similaires à la classe imbriquée statique * Classe B dans l'espace de nommage de A * Amitié forte entre A et B * Accès réciproque aux membres de classe * Différence: toute instance de B est liée à une instance de A * Seule une instance de A peut créer une instance de B * Agrégation: chaque instance de B fait partie d'une instance de A * Agrégation ==> accès réciproque aux membres d'instance * Une classe interne ne peut pas avoir de membres de classe Classe interne (2/3) public class ClasseA { private int a; private static int b; public class ClasseB { private int x; private static int y; ==> interdit public void f() { System.out.println(a); } ==> ok, accès par une instance public void g() { System.out.println(b); } } public static void test1() { ClasseB e = new ClasseB(); ==> interdit, aucune instance ClasseA m = new ClasseA(); ClasseB e = m.new ClasseB(); ==> ok, création par une instance } public void test2() { ClasseB e = new ClasseB(); ==> ok, création par une instance (~ this.new) System.out.println(e.x); } } Classe interne (3/3) * Classe interne ==> problème de distinction des membres * Classe A possède une classe interne B * A et B possèdent toutes les deux un attribut x * Comment faire la distinction dans B ? * this.x ==> attribut de l'instance de classe B * A.x ==> attribut de l'instance de classe A * Même question avec "this" * this ==> instance de classe B * A.this ==> instance de classe A * Classe anonyme = classe interne sans nom * Classe à usage unique * Spécification / héritage à la construction * monAdapteur = new MouseAdapter() { public void mousePressed(MouseEvent e) { /* Redéfinition */ } }; Evénements souris (1/2) * Ecouteur: MouseListener * void mousePressed(MouseEvent) * Bouton enfoncé * void mouseReleased(MouseEvent) * Bouton relâché * void mouseClicked(MouseEvent) * Clic de souris = bouton enfoncé puis relâché * void mouseEntered(MouseEvent) * Arrivée sur un composant * void mouseExited(MouseEvent) * Sortie d'un composant * Adapteur: MouseAdapter * Ajout: addMouseListener(...) Evénements souris (2/2) * Ecouteur: MouseMotionListener * void mouseDragged(MouseEvent) * Déplacement de la souris avec bouton enfoncé * void mouseMoved(MouseEvent) * Déplacement de la souris sans bouton enfoncé * Adapteur: MouseMotionAdapter * Ajout: addMouseMotionListener(...) * Evénement: MouseEvent * int getX() / int getY() * Coordonnées du curseur (relatives au composant qui capte) * int getButton() * Bouton qui a changé d'état Evénements clavier * Ecouteur: KeyListener * void keyPressed(KeyEvent): Touche enfoncée * void keyReleased(KeyEvent): Touche relâchée * void keyTyped(KeyEvent): Saisie d'un caractère * Adapteur: KeyAdapter * Ajout: addKeyListener(...) * Evénement: KeyEvent * char getKeyChar() * Retourne le caractère saisi * int getKeyCode() * Retourne le code de la touche Evénements fenêtre * Ecouteur: WindowListener * void windowClosing(WindowEvent) * Tentative de fermeture de la fenêtre * void windowOpened(WindowEvent) * Première apparition de la fenêtre * Adapteur: WindowAdapter * Exemple: fermeture de la fenêtre addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { dispose(); System.exit(0); } }); Actions * Plusieurs événements peuvent déclencher la même action * Exemple: un bouton et une option de menu * Possibilité de définir un objet "action" * Héritage de la classe "AbstractAction" * Redéfinition de la méthode "actionPerformed" * actionQuitter = new AbstractAction("Quitter") { public void actionPerformed(ActionEvent e) { dispose(); System.exit(0); } }; * L'action peut ensuite être associée à un ou plusieurs événements * Pour certains composants, un événement déclenche une action * Clic sur un composant bouton ==> exécution de l'action associée * Association de l'action: bouton.setAction(actionQuitter); * Evénement ==> appel de la méthode "actionPerformed" de l'action Dessiner sur un composant (1/2) * De préférence, on dessine sur un objet de la classe "JPanel" * Dessiner ==> redéfinition de "paintComponent" * void paintComponent(Graphics g) * Méthode appelée dès que le composant doit être redessiné * Ne doit pas être appelée directement * Pour forcer le réaffichage: appeler "repaint" * Méthode asynchrone * N'est pas forcément exécutée tout de suite * Plusieurs appels dans un instant très court ==> une seule exécution * Lors de la redéfinition, toujours appeler la super-classe * Première ligne: super.paintComponent(g); Dessiner sur un composant (2/2) * Pour dessiner, il faut un contexte graphique * Classe "Graphics" * Objet passé en paramètre à la méthode "paintComponent" * Possibilité aussi de le demander à un composant * composant.getGraphics(); * Permet d'effectuer des opérations de dessin * Couleur: setColor(Color) * Code RGB: Color(int r,int g,int b) * Couleur prédéfinie: Color.YELLOW * Fonte: setFont(Font) * Tracé * drawLine(x1,y1,x2,y2): ligne * drawRect(x,y,l,h) / fillRect(x,y,l,h): rectangle vide / plein * drawOval(x,y,l,h) / fillOval(x,y,l,h): ellipse vide / pleine * drawString(texte,x,y): texte * drawImage(image,x,y,this): image Manipulation des images * Charger une image * Image i = Toolkit.getDefaultToolkit().getImage(url); * Localiser l'image ==> obtenir une URL * Fournir un chemin relatif * Recherche dans les chemins du "classpath" * Application autonome * URL url = getClass().getResource(chemin_fichier); * Applet * URL url = new URL(getCodeBase(),chemin_fichier); * Attendre le chargement d'une image MediaTracker tracker = new MediaTracker(this); tracker.addImage(image,id); try { tracker.waitForID(id); } catch(InterruptedException e) {}
|