Dessiner avec Swing (1): le mécanisme de base

Dessiner avec Swing (1): le mécanisme de base

Introduction

Cette première partie montre l'utilisation correcte des méthodes "paintComponent" et "repaint" dans une application Swing.

La méthode paintComponent

Pour dessiner dans Swing, le système utilise un mécanisme de "callback" ("visite répétée"). Cela signifie, qu'un programme doit mettre le code d'affichage du composant à l'intérieur d'une certaine méthode surchargée, et le Toolkit appelle cette méthode quand il est temps de dessiner. La méthode à surcharger est dans javax.swing.JComponent:

protected void paintComponent(Graphics g) 

Quand le système appelle cette méthode, le paramètre Graphics g est préconfiguré avec l'état adéquat pour le dessin sur ce composant précis:

  • La couleur de l'objet Graphics est préconfigurée suivant la propriété "foreground" du composant.
  • L'écriture de l'objet Graphics est préconfigurée suivant la propriété "font" du composant.
  • La "translation" de l'objet Graphics est préconfigurée de sorte que la coordonné (0,0) représente le coin supérieur gauche du composant.
  • Le rectangle "clip" de l'objet Graphics est préconfigurée suivant la zone du composant qui doit être redessinée.

Les programmes doivent obligatoirement utiliser cet objet Graphics (ou un dérivant) pour dessiner la surface graphique, mais ils sont libres de modifier l'état de l'objet Graphics suivant leurs besoins. Il est utile de savoir que l'objet Graphics dans paintComponent est en réalité un Graphics2D, une extension de Graphics avec des améliorations concernant la gestion de la géométrie, des coordonnés, des couleurs et des textes. Graphics2D est la classe de base pour l'affichage de formes 2D, textes et images sur la plateforme Java. Pour pouvoir l'utiliser dans paintComponent, nous devons convertir l'objet Graphics vers Graphics2D:

Graphics2D g2d  = (Graphics)g;

En général, les programmes doivent éviter de mettre du code de dessin à un endroit où il peut être appelé de l'extérieur du domaine de la méthode paintComponent. Pourquoi? Parce qu'un tel code peut parfois être appelé quand il n'est pas opportun de dessiner -- par exemple avant que le composant soit visible ou avant qu'il a accès à un objet Graphics valable. Il n'est pas recommandé que les programmes appellent paintComponent() directement.

Dans un processus de dessin déclenché par le système, le système demande au composant d'afficher son contenu, normalement pour une des causes suivantes:

  • Le composant est affiché à l'écran pour la première fois.
  • Le composant est modifié dans sa dimension.
  • Le composant a été endommagé et doit être réparé (par exemple une fenêtre qui cachait le composant a été déplacée, et la partie cachée du composant devient visible).

Dans les trois cas, le système appelle automatiquement la méthode paintComponent du composant concerné.

L'exemple de programmation SwingPaintDemo montre l'utilisation simple de la méthode "callback" paintComponent() de Swing:

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

/*
 ***************************************************************
 * Silly Sample program which demonstrates the basic paint
 * mechanism for Swing components.
 ***************************************************************
 */
public class SwingPaintDemo {

    public static void main(final String *  args) {
        Runnable gui =  new Runnable() {

            @Override
            public void run() {
                JFrame f = new JFrame("Aim For the Center");
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                Container panel = new BullsEyePanel();
                panel.add(new JLabel("BullsEye!", SwingConstants.CENTER), BorderLayout.CENTER);
                f.getContentPane().add(panel, BorderLayout.CENTER);
                f.pack();
                f.setLocationRelativeTo(null);
                f.setVisible(true);

            }
        };
        //GUI must start on EventDispatchThread:
        SwingUtilities.invokeLater(gui);
    }
}

/**
 * A Swing container that renders a bullseye background
 * where the area around the bullseye is transparent.
 */
class BullsEyePanel extends JPanel {

    public BullsEyePanel() {
        super();
        setOpaque(false); // we don't paint all our bits
        setLayout(new BorderLayout());
        setBorder(BorderFactory.createLineBorder(Color.black));
    }

    @Override
    public Dimension getPreferredSize() {
        // Figure out what the layout manager needs and
        // then add 100 to the largest of the dimensions
        // in order to enforce a 'round' bullseye
        Dimension layoutSize = super.getPreferredSize();
        int max = Math.max(layoutSize.width, layoutSize.height);
        return new Dimension(max + 200, max + 200);
    }

    @Override
    protected void paintComponent(final Graphics g) {
        super.paintComponent(g);
        Dimension size = getSize();
        int x = 0;
        int y = 0;
        int i = 0;
        while (x < size.width && y < size.height) {
            g.setColor(i % 2 == 0 ? Color.RED : Color.WHITE);
            g.fillOval(x, y, size.width - (2 * x), size.height - (2 * y));
            x += 10;
            y += 10;
            i++;
        }
    }
}

La méthode repaint

Dans le cas d'un processus de dessin déclenché par l'application, c'est le composant qui décide qu'il doit actualiser son contenu, parce que son état interne s'est modifié. Exemple: un bouton remarque que la souris a été actionné et décide qu'il doit dessiner un bouton "enfoncé".

Pour permettre aux applications de déclencher un processus de dessin, le Toolkit fournit la méthode java.awt.Component suivante, pour que les programmes puissent demander un processus asynchrone de dessin:

public void repaint() 

L'appel de la méthode repaint déclenche indirectement l'appel de la méthode paintComponent du composant concerné, mais à un moment qui est jugé le meilleur par le système. On peut en déduire que la méthode repaint ne doit jamais être appelée par la méthode paintComponent elle-même!

Le code suivant montre un exemple simple d'un MouseListener qui utilise repaint() pour déclencher des mises à jour sur un bouton théorique, quand on presse et relâche la souris:

        MouseListener l = new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                MyButton b = (MyButton)e.getSource();
                b.setSelected(true);
                b.repaint();
            }
 
            public void mouseReleased(MouseEvent e) {
                MyButton b = (MyButton)e.getSource();
                b.setSelected(false);
                b.repaint();
            }
        };

La méthode MyButton#setSelected positionne la variable d'instance "selected" qui est utilisée par paintComponent pour savoir ce qu'il faut dessiner.

Dans le cas où paintComponent a besoin d'autres variables ou objets pour dessiner, nous pouvons les transmettre au composant par les paramètres du constructeur, par des méthodes "setter" ou des méthodes semblables. Les références sont alors mémorisées dans des variables d'instance, pour que paintComponent puisse les utiliser sans problème. Le composant suivant utilise par exemple. un objet d'une classe "Animal" (non montrée). L'objet est transmis au composant par le constructeur ou à l'aide de la méthode "setAnimal(..)":

class Cage extends JComponent {
    private Animal animal;

    public Cage(final Animal animal) {
        this.animal = animal;
    }

    @Override
    protected void paintComponent(final Graphics g) {
        super.paintComponent(g);
        g.setColor(Color.BLACK);
        if (animal.getSpecies().equals(Animal.LION)) {
            g.setColor(Color.RED);
        }

        if (animal.getSpecies().equals(Animal.TIGER)) {
            g.setColor(Color.GREEN);
        }

        g.fillOval(animal.getCoord().x, animal.getCoord().y, 10, 10);
    }

    public void setAnimal(final Animal animal) {
        this.animal = animal;
    }
}

//Exemple pour la transmission de l'objet:
Cage cage = new Cage(new Animal(Animal.LION));// par le constructeur
cage.setAnimal(new Animal(Animal.TIGER));// à l'aide de la méthode setAnimal(..)
cage.repaint();

L'appel de repaint sur le composant a pour effet l'appel de la méthode paintComponent, qui se charge de la représentation correcte de l'objet "Animal" actualisé:

La méthode repaint avec arguments

Les composants qui affichent des représentations graphiques compliquées doivent appeler repaint() avec les arguments qui définissent uniquement la zone à actualiser:

public void repaint(int x, int y, int width, int height) 

Ces arguments servent à définir le rectangle "clip", qui représente la zone à actualiser dans l'objet Graphics de la méthode paintComponent.

C'est une erreur générale d'utiliser toujours la version sans arguments, qui redessine toujours le composant dans son intégralité, ce qui conduit souvent à des processus de dessin non nécessaires.
Un exemple pour l'utilisation de repaint() avec arguments est introduit dans la seconde partie de ce tutoriel: Dessiner avec Swing (2): un programme de dessin simple

A voir également
Ce document intitulé « Dessiner avec Swing (1): le mécanisme de base » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous