Bibliothèque graphique
Mathématiques, Informatique, Sciences de la Matière
S1 vendredi matin

Pour faciliter l'accès aux fonctionnalités graphiques de Java, nous avons défini pour ce cours le package graphique. Les types de ce paquet et leurs membres publics ont tous un nom en français afin de les distinguer des types de la bibliothèque standard de Java (API Java 2).

Le tableau ci-dessous précise le nom des paquets dans lesquels sont définis les types cités dans ce document.

Paquet Types
graphique FenetreGraphique
java.lang String
java.awt Image, Shape, Color, Font, FontMetrics, Stroke, BasicStroke
java.awt.geom Arc2D, Ellipse2D, Line2D, Rectangle2D, GeneralPath
java.awt.event KeyEvent, MouseEvent

Installation

Sauvegardez le fichier d'archives graphique.tgz dans votre répertoire ~/info/classes, placez-vous dans ce répertoire et extrayez les fichiers de classe qu'il contient au moyen de :

  tar zxvf graphique.tgz

Ceci crée un répertoire graphique sous ~/info/classes, qui contient les fichiers de classe nécessaires.

Fonctionnalités

Pour pouvoir réaliser des dessins dans une fenêtre, on doit étendre la classe FenetreGraphique du paquet graphique. Cette classe permet d'ouvrir une fenêtre graphique composée de haut en bas d'une barre de menu (avec un menu Fichier), d'une zone de dessin et d'une ligne d'information :

La méthode void dessiner(Graphics2D g2), qui par défaut ne fait rien, est appelée quand une demande d'affichage de la zone de dessin a lieu. Elle doit être redéfinie dans une extension de la classe FenetreGraphique. La re-définition de cette méthode doit contenir les instructions nécessaires à la réalisation du tracé qui apparaîtra dans la zone de dessin. Cette méthode possède un paramètre g2 appelé contexte graphique qui rassemble les attributs courants relatifs au tracé comme par exemple l'épaisseur du trait, la couleur de remplissage, etc.

La méthode void re_dessiner() provoque le ré-affichage du contenu de la fenêtre de dessin : la zone de dessin est effacée puis la méthode précédente est appelée. Cette méthode doit être appelée lorsque le dessin a changé.

Exemple

import java.awt.*;
import graphique.FenetreGraphique;

class Ex1 extends FenetreGraphique {
  Ex1 () {}
  Ex1 (int w, int h, Color c) {super(w,h,c);}

  public void dessiner(Graphics2D g2) {
    g2.setPaint(Color.red);
    g2.drawString("ENPC", 0, getHauteur());
  }
}

class Ex1Main {
  public static void main(String[] args) {
    FenetreGraphique fenêtre = new Ex1();
    fenêtre.ouvrir();
    fenêtre.informer("Un premier exemple");
  }
}

Votre navigateur ne comprend pas la balise <EMBED> !
[source Java]

Système de coordonnées

Les tracés se font dans un repère orthogonal dont l'origine est situé dans le coin supérieur gauche de la zone de dessin, l'axe des x est horizontal et orienté vers la droite, l'axe des y est vertical et orienté vers le bas. Dans ce système, une unité représente 1/72 de pouce quel que soit le périphérique de sortie. Sur l'écran, on peut supposer qu'un pixel représente une unité. Les angles sont repérés par rapport à l'axe Ox (angle 0) et sont positifs dans le sens trigonométrique. Certaines méthodes de la bibliothèque standard de Java prennent un angle exprimé en degrés alors que d'autres méthodes s'attendent à ce qu'il soit exprimé en radians, donc vérifier dans la documentation.

Objets graphiques

Il y a trois types d'objets graphiques :

Votre navigateur ne comprend pas la balise <EMBED> !
[source Java]

Les figures géométriques

Les segments de droite sont représentés par des objets de la classe Line2D. On en obtient une instance en appelant le constructeur suivant :

  Line2D.Float(float x1, float y1, float x2, float y2)
qui crée un segment de droite reliant le point (x1,y1) au point (x2,y2)

Les rectangles sont représentés par des objets de la classe Rectangle2D. On en obtient une instance en appelant le constructeur suivant :

  Rectangle2D.Float(float x, float y, float w, float h)
qui crée un rectangle de largeur w et de hauteur h dont le coin supérieur gauche est situé au point de coordonnées (x,y)

Les ellipses sont représentées par des objets de la classe Ellipse2D. On en obtient une instance en appelant le constructeur suivant :

  Ellipse2D.Float(float x, float y, float w, float h) 
qui crée une ellipse de grand axe w et de petit axe h dont le coin supérieur gauche du rectangle englobant est situé au point de coordonnées (x,y)
Exemple
On dessine généralement un point sous la forme d'un petit cercle :
  void dessinePoint(Graphics2D g2, Point p)
  {
    float d = 5; // diamètre du cercle
    
    // Crée un petit cercle de 5 unités de diamètre, centré en p
    Shape c = new Ellipse2D.Float(p.x-d/2, p.y-d/2, d, d);
    // Fixe la couleur de tracé
    g2.setPaint(Color.yellow);
    // Dessine le cercle
    g2.draw(c);
  }

Les arcs d'ellipses sont représentés par des objets de la classe Arc2D. On en obtient une instance en appelant le constructeur suivant :

  Arc2D.Float(float x, float y, float w, float h, 
              float start, float extent, int type) 
qui crée un arc d'ellipse. L'ellipse est spécifiée par les paramètres x,y,w,h comme ci-dessus. L'arc démarre à l'angle start et s'étend d'un angle extent ; les angles sont comptés positivement à partir de l'axe des x dans le sens trigonométrique et doivent être donnés en degrés. L'argument type sert à spécifier comment l'arc est fermé, il peut prendre l'une des valeurs Arc2D.OPEN, Arc2D.CHORD, ou Arc2D.PIE pour obtenir respectivement un arc ouvert, fermé selon la corde ou selon des rayons partant des extrémités de l'arc (part de gâteau).

Les polygones quelconques sont représentées par des objets de la classe GeneralPath. On en obtient une instance en appelant le constructeur suivant :

  GeneralPath()
crée un chemin vide. Pour ajouter des segments à un chemin, on doit invoquer sur lui l'une des méthodes suivantes :
  void moveTo(float x, float y)
ajoute le point de coordonnées x,y au chemin. Aucun segment ne relie le point courant à ce point.
  void lineTo(float x, float y)
ajoute le point de coordonnées x,y au chemin. Le point courant est relié à ce point par un segment de droite.
  void closePath()
ferme le chemin en reliant par un segment de droite le point courant au point inséré par le dernier appel à moveTo().

Les chaînes de caractères

L'apparence d'une chaîne de caractères dépend de la police utilisée pour l'afficher. Pour avoir des données métriques sur une police f, on doit passer par un objet du type FontMetrics :
  FontMetrics fm = g2.getFontMetrics(f);
g2 est le contexte graphique courant. Sur l'objet fm obtenu, on peut par exemple obtenir la largeur d'une chaîne de caractères s dessinée avec la police f en évaluant l'expression :
  fm.stringWidth(s)

Les images

La classe Image possède les méthodes suivantes :
  int getWidth(ImageObserver obs)
retourne la largeur en pixels de cette image. Le paramètre obs doit recevoir la valeur null.
  int getHeight(ImageObserver obs)
retourne la hauteur en pixels de cette image. Le paramètre obs doit recevoir la valeur null.

Les attributs

C'est le contexte graphique (objet de la classe Graphics2D) qui rassemble les attributs nécessaires au dessin. Une instance du contexte graphique courant est passé en paramètre à la méthode dessiner(), on peut donc modifier un attribut en invoquant l'une des méthodes setXXX() ci-dessous sur cette instance, par exemple :
  g2.setPaint(Color.blue);
g2 est le contexte graphique courant.

Couleur

Une couleur est un objet du type Color, sous-type de Paint. Quelques couleurs sont prédéfinies et associées au nom anglais qui désigne cette couleur : Color.black, Color.gray, Color.blue, etc. D'autres couleurs peuvent être obtenues par mélange en proportions diverses des couleurs rouge, verte et bleue. Le constructeur
  Color(float r, float v, float b) 
crée une couleur composée d'une proportion r de rouge, v de vert et b de bleu. Les proportions doivent être dans l'intervalle $[0,1]$. L'attribut de couleur est fixée par la méthode
  void setPaint(Paint paint)

Largeur du trait

Il est possible de spécifier plusieurs attributs de tracé, le plus important étant la largeur du trait. Ces attributs sont rassemblés au sein de la classe BasicStroke (sous-type de Stroke) que l'on peut interpréter comme un crayon virtuel. Le constructeur suivant :
  BasicStroke(float w)
crée un crayon dont la largeur de tracé est w. La largeur du trait est fixée par la méthode
  void setStroke(Stroke s)

Police de caractères

Une police de caractères est un objet du type Font caractérisé par un nom, un style et une taille. Le constructeur suivant
  Font(String nom, int style, int taille)
crée une nouvelle police. Le paramètre nom est le nom logique d'une police, les noms disponibles sont "Dialog", "DialogInput", "Monospaced", "Serif", "SansSerif" ou Symbol. Le paramètre style peut prendre la valeur Font.PLAIN pour le style standard, Font.BOLD pour avoir des caractères gras ou Font.ITALIC pour avoir des caractères penchés ou bien la combinaison Font.BOLD | Font.ITALIC pour une combinaison ces deux styles. Le paramètre taille indique la taille en points de cette police, le << point >> valant approximativement $1/72$ de pouce. La police de caractères est fixée par la méthode
  setFont(Font font)

Les méthodes de tracé

À chaque type d'objets graphiques est associé au moins une méthode de tracé. Ces méthodes réalisent le rendu graphique de l'objet sur un périphérique de sortie. Elles sont à appliquer sur le contexte graphique courant après avoir fixé les attributs relatifs au tracé.

Dessiner un contour

  void draw(Shape s)
trace le contour de la forme géométrique s en utilisant les attributs de couleur et de tracé du contexte graphique courant.

Remplir l'intérieur d'un contour

  void fill(Shape s)
remplit l'intérieur de la forme géométrique s dans la couleur courante.

Écrire du texte

  void drawString(String s, float x, float y)
dessine le texte spécifié par la chaîne s dans la couleur et la police de caractères courantes. L'origine de la ligne de base du premier caractère est à la position x,y.

Inclure une image

  boolean drawImage(Image img, int x, int y, ImageObserver obs)
dessine l'image img. Le coin supérieur gauche de l'image est placée à la position x,y. Le dernier paramètre doit recevoir la valeur null. On obtient un objet de type Image par un appel à la méthode chargerImage().

Événements clavier

Un événement clavier est un objet de type KeyEvent qui est produit lorsque l'utilisateur tape sur une touche de son clavier. Ce type est défini dans le paquet java.awt.event.

Il y a trois sortes d'événements clavier correspondant respectivement à l'enfoncement d'une touche, le relâchement d'une touche et la séquence enfoncement / relâchement d'une touche associée à un caractère. Le traitement d'un événement est confié à une méthode ayant un prototype standardisé, spécialisée pour un type donné d'événement.

Ci-dessous, quelques méthodes utiles définies sur ce type.

  char getKeyChar()
retourne le caractère associé à la touche. Pour une touche non associée à un caractère, retourne la valeur de la constante CHAR_UNDEFINED.

  int getKeyCode()
retourne le code clavier associé à la touche. Une constante de type int est définie pour chacune des touches du clavier. Par exemple, la touche A a pour code clavier la valeur de la constante VK_A.
 
  boolean isShiftDown()
  boolean isControlDown()
  boolean isMetaDown() 
retourne l'état (enfoncé ou non) des touches Shift, Control et Meta respectivement.

Événements souris

Un événement souris est un objet de type MouseEvent qui est produit lorsque l'utilisateur déplace sa souris ou clique sur un bouton. Ce type est défini dans le paquet java.awt.event. Il y a plusieurs sortes d'événements souris :

Le traitement d'un événement est confiée à une méthode ayant un prototype standardisé, spécialisée pour un type donné d'événement.

Quelques méthodes définies sur ce type

  int getClickCount() 
retourne le nombre de clics : 1 pour un simple clic, 2 pour un double, etc.
  int getX() 
  int getY() 
  Point getPoint() 
retourne les coordonnées (x,y) du pointeur dans le repère de la zone de dessin.
 
  boolean isShiftDown()
  boolean isControlDown()
  boolean isMetaDown() 
retourne l'état (enfoncé ou non) des touches Shift, Control et Meta respectivement.
int getModifiers()
retourne un entier indiquant l'état des boutons de la souris. Par exemple, l'expression suivante est vraie si le bouton 2 de la souris a été enfoncé :
  (e.getModifiers() & InputEvent.BUTTON2_MASK) ==
  InputEvent.BUTTON2_MASK
e est un objet de type MouseEvent.

Un exemple

Pour saisir à la souris un ensemble de points, on déclare une liste d'objets à laquelle chaque événement " clic de bouton gauche" va ajouter un point (ou bien va supprimer tous les points, pour tout autre clic) :
  List points = new ArrayList();

  public void mouseClicked(MouseEvent e)
  {
    // Si clic gauche, ajoute un point à la liste
    if ((e.getModifiers() & InputEvent.BUTTON1_MASK) 
        == InputEvent.BUTTON1_MASK)
      points.add(e.getPoint());
    else // Sinon retire tous les points de la liste
      points.clear();
    
    // Demande le ré-affichage de la liste de points
    re_dessiner();
  }
Votre navigateur ne comprend pas la balise &lt;EMBED&gt; !
[source Java]

Ajouter d'autres composants d'interface

Il est possible d'ajouter d'autres items de menu au menu existant et d'autres menus à la barre de menus de la façon suivante :

import java.awt.*;
import javax.swing.*;
import graphique.FenetreGraphique;

class Ex4 extends FenetreGraphique {
  JMenuBar jmb;
  JMenu jm0, jm1;
  JMenuItem jmi0, jmi1;
  Ex4 () {
    étendreMenus();
  }
  Ex4 (int w, int h, Color c) {
    super(w,h,c); 
    étendreMenus();
  }

  void étendreMenus() {
    // la barre de menu
    jmb = getJMenuBar();

    // le premier menu
    jm0 = jmb.getMenu(0);
    // addition d'un item de menu
    jmi0 = new JMenuItem("Item de menu");
    jm0.add(jmi0);

    // addition d'un menu
    jm1 = new JMenu("Nouveau menu");
    jmi1 = new JMenuItem("Item de menu");
    jm1.add(jmi1);
    jmb.add(jm1);
  }

  // ...
}

exemple d'extension

Ouvrir une fenêtre de dialogue

Une fenêtre de dialogue permet de saisir des données.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import graphique.FenetreGraphique;

class Ex6 extends FenetreGraphique {
  Ex6 () {
  }

  Ex6 (int w, int h, Color c) {
    super(w,h,c); 
  }

  public void mouseClicked(MouseEvent e)
  {
    // Si clic droit, ouvre une fenêtre de saisie
    if ((e.getModifiers() & InputEvent.BUTTON3_MASK) 
        == InputEvent.BUTTON3_MASK) {
      Saisie dialogue = new Saisie(this);
      dialogue.pack();
      dialogue.setLocationRelativeTo(this);
      dialogue.setLocation(e.getPoint());
      dialogue.show();
    }
  }

  public void dessiner(Graphics2D g2) {
    g2.setPaint(Color.red);
    g2.drawString("ENPC", 0, getHauteur());
  }
}

class Ex6Main {
  public static void main(String[] args) {
    FenetreGraphique fenêtre = new Ex6();
    fenêtre.ouvrir();
    fenêtre.informer("Exemple avec fenêtre de saisie");
  }
}

class Saisie extends JDialog {
  FenetreGraphique parent;
  Container conteneur;
  GridLayout placement = new GridLayout(3,2);
  JLabel champ1Label = new JLabel("Le champ 1");
  JTextField champ1Text = new JTextField();
  JLabel champ2Label = new JLabel("Le champ 2");
  JTextField champ2Text = new JTextField();

  Action valider = new AbstractAction("Valider") {
    public void actionPerformed(ActionEvent e) {
      parent.informer(champ1Text.getText() + ", " 
		      + champ2Text.getText());
      dispose();
    }
  };
                          
  JButton validerButton = new JButton(valider);

  Action effacer = new AbstractAction("Effacer") {
    public void actionPerformed(ActionEvent e) {
      champ1Text.setText("");
      champ2Text.setText("");
    }
  };
                          
  JButton effacerButton = new JButton(effacer);

  // vérifie que les champs saisis sont numériques
  KeyListener kl = new KeyAdapter() {
      public void keyTyped(KeyEvent e) {
	char c = e.getKeyChar();
	if (!((c >= '0') && (c <= '9') ||
	      (c == KeyEvent.VK_BACK_SPACE) ||
	      (c == KeyEvent.VK_DELETE))) {
	  getToolkit().beep();
	  e.consume();
	}
      }
    };

  public Saisie(FenetreGraphique parent) {
    super((Frame)null, "Saisie des paramètres", true);
    this.parent = parent;
    this.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    conteneur = getContentPane();
    conteneur.setLayout(placement);
    this.setSize(new Dimension(400, 300));
    this.setTitle("Saisie");
    placement.setHgap(20);
    placement.setVgap(10);

    champ1Text.addKeyListener(kl);
    champ2Text.addKeyListener(kl);

    conteneur.add(champ1Label);
    conteneur.add(champ1Text);
    conteneur.add(champ2Label);
    conteneur.add(champ2Text);
    conteneur.add(validerButton);
    conteneur.add(effacerButton);
  }
}

Last modified: Mon Nov 26 12:13:34 MET 2001