feuille10

publicité
C. Recanati
InHM Java
MI1, 2012
TP 10
1) Reprendre d’abord l’exercice 2 de la feuille 9 (applet sur une
JList) si vous ne l’avez pas fait.
2) On va maintenant transformer le programme Sketcher pour en
faire un logiciel de dessin. On va dessiner dans la fenêtre au lieu
d’imprimer des infos sur les événements se produisant sur les
menus. Le JTextArea va donc être remplacé par un JPanel qui
captera les clics de souris, et permettra de dessiner interactivement.
A. Pour réaliser le logiciel, on va implémenter l’architecture
Modèle/Vue introduite en cours, en définissant les deux classes
SketcherView et SketcherModel (cf. poly).
Un modèle de feuille de dessin sera défini par la classe
SketcherModel comme une liste d’éléments (des formes
colorées). La vue SketcherView (un JPanel étendu) gèrera
l’affichage du modèle mais aussi l’interaction de l’utilisateur avec le
logiciel. On va implanter dans SketcherView la création d’un
nouvel élément (temporaire du point de vue interactif), et son ajout
au modèle à la fin de l’interaction de l’utilisateur avec la souris.
Cette « vue » (un JPanel) sera insérée dans le panneau de contenu
du cadre de l’application, comme indiqué dans le poly.
B. Avant d’implanter la partie interactive de l’application
permettant d’introduire un élément de dessin, on va définir la classe
abstraite Element qui définit les différentes formes d’éléments
d’une feuille de dessin.
Pour définir un élément, on a besoin de deux champs : une forme
(basée sur l’interface Shape de javax.swing) et une couleur. On va
ici définir une classe abstraite Element, avec des sous-classes
internes statiques qui implémentent cette classe abstraite (une sousclasse Ligne, une sous-classe Rectangle, etc.). Ces sous-classes
d’éléments (Ligne, Rectangle, etc.) - seront donc définies dans la
classe abstraite Element comme classes internes, et accessibles dans
le code avec l’opérateur point « . ». On aura ainsi accès à la classe
Ligne en écrivant Element.Ligne, à la classe Rectangle avec
Element.Rectangle.
Les constructeurs d’une classe d’éléments prendront 3 arguments :
un début et une fin (de type Point) pour positionner la forme, et un
argument couleur (de type Color). Ces constructeurs
« mémoriseront » la forme concrète créée dans un membre privé
avec un type spécifique (ex : Line2D.Double pour une Ligne). Un
objet de type Shape pourra néanmoins ensuite être retourné par
l’accesseur getShape(), en « castant » la valeur de ce champ privé.
En vous inspirant du code de la classe interne Element.Ligne qui
suit, écrire le code de la classe interne Element.Rectangle.
// Element.java
import java.awt.*;
import java.awt.geom.*;
public abstract class Element {
protected Color color; // un élément a tjs une couleur
public Element(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
// un élément a une forme récupérable par getShape() :
public abstract Shape getShape();
// un élément a aussi un plus petit rectangle englobant
// récupérable par getBounds() :
public abstract java.awt.Rectangle getBounds();
// + une méthode modify pour positionner l’élément :
public abstract void modify(Point debut, Point fin);
// on définit une sous-classe « concrète » de Element
// accessible par Element.Ligne
public static class Ligne extends Element {
private Line2D.Double ligne;
// ça sera sa forme Shape
public Ligne(Point debut, Point fin, Color couleur){
super(couleur);
ligne = new Line2D.Double(debut, fin);
}
public Shape getShape() {
return ligne;
}
public java.awt.Rectangle getBounds() {
return ligne.getBounds();
}
public void modify(Point debut, Point fin) {
ligne.x1 = debut.x;
ligne.y1 = debut.y;
ligne.x2 = fin.x;
ligne.y2 = fin.y;
}
}
// vient ensuite le code implémentant Element.Rectangle
// vient ensuite le code implémentant Element.Cercle
// etc. pour tous les types d’élements
}
La classe abstraite Element inclut un membre de type Color (avec
un accesseur getColor() et un constructeur qui initialise ce
membre) et déclare des méthodes abstract getShape() et
abstract getBounds() retournant respectivement la forme
correspondant à l’élément (un rectangle, cercle, etc.) et son rectangle
englobant. Ce rectangle retourné par getBounds servira, lors de
l’affichage d’un élément, à limiter la zone impliquée par l’affichage.
On définit également une méthode abstraite modify(Point
debut, Point fin) permettant de positionner un élément en
spécifiant ces deux nouvelles extrémités.
C. Dans cette partie, on va implémenter la création interactive d’un
élément temporaire dans la vue (SketcherView.java), et laisser en
commentaire la partie concernant le modèle.
On gèrera avec un MouseHandler qui étend
MouseInputAdapter la réaction du logiciel à l’enchaînement des
événements souris permettant de dessiner. Ces événements sont un
premier clic de souris, suivi d’un déplacement de souris avec le
bouton enfoncé (drag), suivi finalement du relâchement du bouton.
Ces événements vont permettre à l’utilisateur de dessiner
interactivement un nouvel élément (de type ligne, rectangle, etc.
selon le mode activé et mémorisé dans la variable elementType).
- Sur le clic : on enregistre le point de début du dessin (pour un
élément temporaire tempElement).
- Sur le mouvement : a. on efface l’élément temporaire
précédemment dessiné; b. on enregistre le point du curseur courant
comme extrémité du nouveau dessin; et c. on trace le nouvel
élément temporaire.
- Sur le relâcher : on réinitialise les variables pour une nouvelle
utilisation (dessin d’un autre élément) et on stocke l’élément tracé
précédemment dans le modèle.
 1. Pour dessiner (ou effacer, c’est pareil !), on utilise la méthode
générique draw d’un objet Shape de graphics2D en mode XOR
(utiliser setXorMode) . On mémorisera l’élément temporaire dans
un membre de la classe MouseHandler ainsi que les points de
début et de fin pour pouvoir continuer le dessin d’événement en
événement.
2. Pour faire référence aux constantes qui identifient le type
d’élément (LIGNE, RECTANGLE, etc.), on modifiera la classe
SketcherView pour qu’elle implémente l’interface Constants.
Elements de correction
// SketcherView.java (exercice 2)
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
class SketcherView extends JPanel implements Observer,
Constants {
private Sketcher theApp; // Objet Application
public SketcherView(Sketcher theApp) {
this.theApp = theApp;
setBackground(Color.white); setOpaque(true);
setBorder(BorderFactory.createLineBorder(Color.black));
MouseHandler handler = new MouseHandler();
addMouseListener(handler);
addMouseMotionListener(handler);
}
public void update(Observable o, Object rectangle) {
// code correspondant aux modifs du modèle
if (rectangle == null)
repaint();
else repaint( (Rectangle) rectangle);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2D = (Graphics2D) g;
Iterator elements = theApp.getModel().getIterator();
Element element;
while (elements.hasNext()) {
element = (Element) elements.next();
g2D.setPaint(element.getColor());
g2D.draw(element.getShape());
}
}
class MouseHandler extends MouseInputAdapter {
private Point debut; // position au clic
private Point dernier; // position en drag
private Element tempElement ;
private Graphics2D g2D;
public void mousePressed (MouseEvent e) {
// clic du bouton de souris
debut = e.getPoint();
System.out.println("premier clic");
g2D = (Graphics2D) getGraphics();
g2D.setPaint(theApp.getWindow().getElementColor());
g2D.setXORMode(Color.white);
}
public void mouseDragged (MouseEvent e) {
// deplacement de souris
dernier = e.getPoint();
if (tempElement == null) {
tempElement = createElement(debut, dernier);
}
else {
g2D.draw(tempElement.getShape());
// effacer le precedent
tempElement.modify(debut, dernier);
// modifier l'element
}
g2D.draw(tempElement.getShape());
// dessiner le nouveau
}
public void mouseReleased (MouseEvent e) {
// relachement de la souris
System.out.println("souris relachee");
if (tempElement != null) {
theApp.getModel().add(tempElement);
tempElement = null;
}
if (g2D != null) {
g2D.dispose();
g2D = null;
}
debut = dernier = null;
}
private Element createElement(Point debut, Point fin) {
switch(theApp.getWindow().getElementType()) {
case LIGNE:
return new Element.Ligne(debut, fin,
theApp.getWindow().getElementColor());
case RECTANGLE:
return new Element.Rectangle(debut, fin,
theApp.getWindow().getElementColor());
case CERCLE:
return new Element.Cercle(debut, fin,
theApp.getWindow().getElementColor());
case COURBE:
return new Element.Courbe(debut, fin,
theApp.getWindow().getElementColor());
}
return null;
}}}
Téléchargement