licence informatique – Château Gombert – Programmation Orientée Objet : Travaux dirigés : Séances dédiées à la programmation graphique sous JAVA Suite et fin 1.7 : Dessiner et écrire Seuls certains objets graphiques Swing ou AWT peuvent recevoir des contenus textuels et/ou dessinés. Nous allons nous intéresser ici à quelques uns de ces objets. Pour dessiner, le peintre n’utilise pas le cadre, mais la toile. En java, avec AWT c’est le même processus : on ne dessine pas dans une Frame, mais dans un Canvas. le Canvas doit être étendu par une classe définie par nous. Avec Swing (notre choix), il n’est heureusement pas nécessaire de passer par cette étape : on utilise une toile toute prête telle que je JPanel: -créons une JFrame -ajoutons lui un JPanel (ou un objet qui étend JPanel) - dès qu’un évenement visuel se produit (la fenêtre est redimensionnée, salie par une fenêtre qui passe devant, modifiée…), la méthode repaint du JPanel est appelée par le système. Cette méthode déclenche un appel à deux méthodes qui nous intéressent particulièrement : update et paint. Si nous voulons dessiner quelque chose dans notre JPanel, il nous faut redéfinir ces méthodes. Voici un premier exemple avec la classe Circuit: class Circuit extends JPanel implements MouseListener, MouseMotionListener { //… il nous faut redéfinir les méthodes ci-dessous … // remarquez le paramètre ‘Graphics g’ : cette variable est l’espace où nous pouvons dessiner. public synchronized void paint(Graphics g) { update(g); // c’est tout pour paint (update fait le reste !!!) } public synchronized void update(Graphics g) { //ici, un exemple simpliste qui écrit ‘coucou’ en jaune à l’emplacement (10,10): g.setColor(Color.yellow); g.drawString(“Coucou !”, 10, 10); } } 1.7.a : des couleurs et des crayons Dans le premier exemple ci-dessus, tout dessin ‘utilisateur’ est réalisé par la méthode update grâce à la variable g de type Graphics qui est fournie par le système au moment nécessaire. Il nous est bien sûr permis de déléguer à d’autres objets le droit de se peindre dans ce contexte graphique g. (nos composant, par exemple devraient être dotés d’une méthode pour se peindre recevant en paramètre un contexte graphique g). Le circuit n’aura alors plus qu’à parcourir le tableau de composants et leur demander de se redessiner. Ne brûlons pas les étapes. Apprenos d’abord le b.a.-ba du dessin. Dans la méthode update de notre circuit, il est maintenant permis d’appeler toutes les méthodes du contexte graphique g. Celui-ci permet d’accéder à une boite à outils (polices de caractères…) et à une palette de couleurs : A tout moment, on peut changer d’outil et de couleur avec les commandes suivantes : g.setFont( objet de type Font) ; g.setColor( objet de type Color) ; Jongler avec les Font et les Color et assez difficile si on souhaite aller loin, mais si on n’est pas exigeant, JAVA fournit quelques polices par défaut assez simples et un jeu de constantes de couleur acceptables. Ex : g.setFont(new Font("Dialog", Font.PLAIN, 10)) ; g.setColor( Color.black) ; //constante prédéfinie Avec nos couleurs et nos polices de caractères, il nous est maintenant possible de dessiner avec une variable de type Graphics: 1.7.b : des lignes, des points etc… Des lignes de plus en plus complexes: drawLine(int x1, int y1, int x2, int y2) drawOval(int x1, int y1, int width, int height) drawRect(int x1, int y1, int width, int height) Vous pouvez aussi dessiner des formes pleines avec les méthodes du type fillOval(int x1, int y1, int width, int height) fillRect(int x1, int y1, int width, int height) etc… (cf http://java.sun.com/j2se/1.3/docs/api/java/awt/Graphics.html) 1.7.c : du texte Les chaines de caractères peuvent aussi bien se dessiner grâce aux polices choisies (on peut aussi ne pas choisir de police et laisser faire JAVA). g.drawString(String chaine, int x, int y) ; 1.8 : Dessiner une image Nous avons besoin d’aller un peu plus loin avec le contexte graphique : par exemple précharger une image et l’écrire au moment opportun. 1.8.a : Une image venant d’un fichier JAVA connaît par défaut un nombre restreint de formats graphiques, mais cela nous suffira. Les formats ‘gif’ et ‘jpeg’ sont facilement utilisables. Les ressources du projet sont de type ‘gif’ avec une couleur de transparence. Voyons quelques étapes du dessin de ce genre d’image. L’objet Image en JAVA doit être étendu. Nous allons utiliser l’objet ImageIcon qui est très pratique: ImageIcon i=new ImageIcon(System.getProperty("user.dir") + "/MicroLogic/ressources/AND.gif"); Observons au passage l’appel à la fonction System.getProperty(“user.dir”) qui renvoit le chemin depuis lequel le programme a été lancé. Cette chaine de caractères nous permet de retrouver notre ressource de type image : System.getProperty("user.dir") + "/MicroLogic/ressources/AND.gif" est une chaine de caractères menant à un fichier contenant l’image de la porte ET. Une image de type ImageIcon est facilement duplicable : ImageIcon j=new ImageIcon(i.getImage()); Dessiner une image de ce type est maintenant un jeu d’enfant: si par exemple nous disposons d’un objet img de type ImageIcon, nous pouvons écrire dans la méthode update du circuit (de type JPanel): //dessiner l’image img en (10,13) sur le contexte graphique g avec le JPanel ‘this’ comme conteneur g.drawImage(img, 10, 13, this); remarque 1 : la méhode drawImage a besoin d’un paramètre de type ImageObserver. Nous lui fournissons à la place le composant de type Circuit qui est un JPanel qui étend Jcomponent qui étend à son tour Component qui implément correctement ImageObserver. On doit fournir un objet ImageObserver cohérent (un composant qui contiendra l’image) à cette méthode, si on ne veut pas avoir de surprises au moment du dessin. Remarque 2 : si vous souhaitez déléguer le dessin des images aux objets de type Composant, il sera nécessaire que tous les composants aient connaissance de l’objet Circuit qui les contient afin de pouvoir appeler correctement cette méthode drawImage qui a besoin d’un JPanel (cela nécessitera une petite gymnastique au moment de la création des objets). Remarque 3 à propos du projet de Circuits logiques. Les images que vous allez charger vérifient certaines contraintes : elles font toutes 49 pixels * 49 pixels. Les extrémités correspondant aux entrées et sorties sont positionnées très clairement à un endroit bien précis. Vous devrez gérer des tableaux de points pour ces extrémités pour chaque composant en fonction de l’image de ce composant. Grâce à ce tableau, vous pourrez ensuite tracer les lignes de connection… Notez que vous aurez très vite des problèmes de cables qui se croisent (ce qui est normal) car le tracé de graphes dont les arcs ne se coupent pas ou dont les arcs contournent les nœuds est un problème excessivement complexe qui sera abordé avec des algorithmes et des heuristiques difficiles, en Maîtrise d’informatique ou plus tard encore. C’est un sujet de recherche à part entière. 1.8 : Technique du double-buffer Un problème plus facile à résoudre va se poser très rapidement : dès qu’un composant est déplacé, tout le circuit est repeint et l’ensemble clignote de manière peu agréable. En effet, la méthode repaint du JPanel efface tout le contenu, puis redessine tout composant par composant. Une technique consiste à ne pas dessiner directement dans le contexte graphique g dont nous avons parlé jusqu’ici, mais à dessiner dans une image de la taille de ce contexte graphique. Cette image est ensuite recopiée d’un seul coup dans le contexte graphique, ce qui évite le clignotement des objets. La technique s’appelle celle du ‘double-buffer’. Elle est assez classique pour mériter quelques détails : voici son déroulement. //dans le circuit, prenons quelques variables d’instance : Image offscreen= null; Dimension offscreensize= getSize(); Graphics offgraphics= null; //réécrivons la méthode update pour tenir compte de ces variables : public synchronized void update(Graphics g) { //recopiez exactement ce code … Dimension d= getSize(); //gérer le double buffer dans le cas où il doit être modifié ou recréé if ((offscreen == null) || (d.width != offscreensize.width) || (d.height != offscreensize.height)) { offscreen= createImage(d.width, d.height); offscreensize= d; offgraphics= offscreen.getGraphics(); } //…jusqu’ici //puis dessinez ce que vous voulez dans le double buffer offgraphics.setColor(Color.yellow); offgraphics.drawString(“coucou”, 15, 25); //ici, vous pourriez dessiner un cadre vert, le nom du circuit //puis effectuer une boucle parcourant le tableau de composants du circuit // et appelant pour chacun d’eux la méthode paint avec offGraphics comme paramètre au //lieu de g. Par exemple : for (int i =0 ;i<nb_composants;i++) Composants[i].paint(offgraphics); //enfin, recopiez exactement le code ci-dessous : paintComponents(offgraphics); //Paint any components that have been added to this panel g.drawImage(offscreen, 0, 0, null); //…jusqu’ici. } Vous apprécierez la différence avec un contexte graphique directement modifié. 1.9 : Conclusion Il reste beaucoup à découvrir avec Swing. Vous avez maintenant les outils nécessaires pour réaliser le projet dans sa version finale. N’hésitez pas à utiliser les tutoriels Swing fournis par Sun lorsque vous rencontrez des difficultés.