Malen in Swing Teil 1: der grundlegende Mechanismus

Aus Byte-Welt Wiki
Zur Navigation springenZur Suche springen

Dieser Artikel zeigt den korrekten Gebrauch der Methoden "paintComponent" und "repaint" in einer Swing Anwendung.

Die paintComponent Methode

Zum Malen in Swing wird ein "callback" Mechanismus benutzt ("Wiederholungsbesuch"). Das bedeutet, daß ein Programm den Darstellungscode der Komponente innerhalb einer bestimmten überschriebenen Methode setzen sollte, und das Toolkit ruft diese Methode auf, wenn es Zeit ist zu malen. Die zu überschreibende Methode ist in javax.swing.JComponent:

<code=java>protected void paintComponent(Graphics g)</code=java>

Wenn das System diese Methode aufruft, ist der Parameter Graphics g mit dem passenden Zustand für das Malen auf dieser bestimmten Komponente vorkonfiguriert:

  • Die Farbe des Graphics Objektes wird auf die foreground-Eigenschaft der Komponente eingestellt.
  • Die Schriftart des Graphics Objektes wird auf die font-Eigenschaft der Komponente eingestellt.
  • Die "translation" des Graphics Objektes wird so eingestellt, daß die Koordinate (0,0) die obere linke Ecke der Komponente darstellt.
  • Das "clip"-Rechteck des Graphics Objektes wird auf den Bereich der Komponente eingestellt, der neu gezeichnet werden muss.

Programme müssen dieses Graphics Object verwenden (oder ein von ihm abgeleitetes) um die Oberfläche darzustellen. Sie sind frei, den Zustand des Graphics Objektes so zu ändern, wie es benötigt wird. Dabei ist es nützlich zu wissen, daß das Graphics Objekt in paintComponent eigentlich ein Graphics2D ist, eine Erweiterung von Graphics mit Verbesserungen bezüglich Steuerung der Geometrie, Umwandlungen von Koordinaten, Farbenverwaltung und Textdarstellung. Graphics2D ist die grundlegende Klasse zur Wiedergabe von 2D Formen, Text und Bildern auf der Java Plattform. Um sie in paintComponent benutzen zu können, müssen wir lediglich das Graphics Objekt nach Graphics2D "casten": <code=java>Graphics2D g2d = (Graphics2D)g;</code=java>

Im allgemeinen sollten Programme es vermeiden, darstellenden Code an irgendeinen Punkt zu setzen, wo er von außerhalb des Bereichs der paintComponent Methode aufgerufen werden könnte. Warum? Weil solcher Code manchmal aufgerufen werden kann, wenn es nicht angebracht ist zu malen -- zum Beispiel bevor die Komponente sichtbar ist oder Zugang zu einem gültigen Graphics Objekt hat. Es wird nicht empfohlen, daß Programme paintComponent() direkt aufrufen.

In einem systemausgelösten Malvorgang, bittet das System eine Komponente, ihren Inhalt darzustellen, normalerweise aus einem der folgenden Gründe:

  • Die Komponente wird zum erstenmal sichtbar auf dem Bildschirm abgebildet.
  • Die Komponente wird in der Größe verändert.
  • Die Komponente wurde beschädigt und muß repariert werden (zum Beispiel wurde etwas verschoben, das vorher die Komponente verdeckte, und ein vorher verdeckter Teil der Komponente wird sichtbar).

In allen drei Fällen ruft das System automatisch die paintComponent Methode der Komponente auf.

Das SwingPaintDemo Programmbeispiel zeigt den einfachen Gebrauch der paintComponent() callback Methode von Swing:

<code=java>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++;
       }
   }

}</code=java>

Die repaint Methode

Bei einem anwendungsausgelösten Malvorgang entscheidet die Komponente, daß sie ihren Inhalt aktualisieren muß, weil sich ihr interner Zustand geändert hat. Beispiel: ein Button erkennt, daß eine Maustaste betätigt worden ist und stellt fest, daß er einen "niedergedrückten" Button malen muß.

Um anwendungsausgelöstes Malen zu ermöglichen, liefert das Toolkit die folgende java.awt.Component Methode, damit Programme einen asynchronen Malvorgang anfragen können:

<code=java> public void repaint() </code=java>

Der Aufruf der repaint Methode veranlasst indirekt den Aufruf der paintComponent Methode der angesprochenen Komponente, und zwar zu dem für das System am geeignetsten Zeitpunkt. Daraus folgt, daß die repaint Methode natürlich niemals von der paintComponent Methode selbst aufgerufen werden sollte!

Der folgende Code zeigt ein einfaches Beispiel von einem MouseListener, der repaint() benutzt, um Updates auf einem theoretischen Button auszulösen, wenn die Maus gedrückt und losgelassen wird:

<code=java> 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();
           }
       };</code=java>

Die Methode MyButton#setSelected setzt die Instanzvariable "selected" welche von paintComponent benutzt wird, um die Ausgabe zu steuern. Falls paintComponent noch andere Variablen oder Objekte zum Zeichnen benötigt, können wir sie der Komponente über Konstruktorparameter oder Settermethoden übergeben. Die Referenzen werden dann in Instanzvariablen gespeichert, so daß paintComponent problemlos auf sie zugreifen kann. Die folgende Komponente benutzt z.B. ein Objekt einer (nicht gezeigten) Klasse "Animal", welches im Konstruktor oder mit Hilfe der Methode "setAnimal(..)" an die Komponente übergeben wird: <code=java>class Savannah extends JComponent {

   private Animal animal;
   public Savannah(Animal animal) {
       this.animal = animal;
   }
   @Override
   public void paintComponent(Graphics g) {
       super.paintComponent(g);
       g.setColor(Color.BLACK);
       if (animal.getSpecies().equals("Lion")) {
           g.setColor(Color.RED);
       }
       if (animal.getSpecies().equals("Antilope")) {
           g.setColor(Color.GREEN);
       }
       g.fillOval(animal.coord.getX(), animal.coord.getY(), 10, 10);
   }
   public void setAnimal(Animal animal) {
       this.animal = animal;
   }

} ... //Beispiel für die Objektübergabe: Savannah savannah = new Savannah(new Animal("Lion"));// im Konstruktor ... savannah.setAnimal(new Animal("Antilope"));// mit Hilfe der Methode setAnimal(..) ...</code=java> Der Aufruf von repaint auf der Komponente veranlasst dann die paintComponent Methode das aktuelle Objekt korrekt zu malen.

Komponenten, die komplizierte Darstellungen ausgeben, sollten repaint() mit den Argumenten aufrufen, welche nur den Bereich definieren, der eine Aktualisierung erfordert:

<code=java> public void repaint(int x, int y, int width, int height) </code=java>

Diese Argumente dienen dazu, das "clip"-Rechteck zu definieren, das im Graphics Objekt der paintComponent Methode den Bereich darstellt, der aktualisieren muss.

Es ist ein allgemeiner Fehler, immer die keine-Argumente Version zu benutzen, die immer die gesamte Komponente neu zeichnet, was häufig zu nicht notwendigen Malverarbeitungen führt. Ein Beispiel für die Verwendung von repaint() mit den Argumenten finden wir im zweiten Teil dieses Tutorials.