Die Operation "paintComponent" implementieren
Swing kennt kein spezielles Objekt für Zeichnungen (Graphiken, Kurven oder Diagramme), das man mit einem Zeichengeräte (Plotter) vergleichen könnte.
Um eine Zeichnung zu erstellen, wird statt dessen das Erscheinungsbild einer Komponente so aufgebaut, daß die gewünschte Zeichnung entsteht.
Ein Objekt, das eine Zeichenfläche repräsentieren soll, wird im Englischen auch als canvas („Leinwand“) bezeichnet.
Die Festlegung des Erscheinungsbildes
In der Klasse »javax.swing.JComponent« wird die Methode »paintComponent( Graphics g )« definiert.
paintComponent [MethodSummary]
javax.swing
Class JComponent
protected void paintComponent( Graphics g )
Calls the UI delegate's paint method, if the UI delegate is non-null.
Die Methode "paintComponent( Graphics g )" ruft normalerweise die Zeichenmethode des Oberflächenobjekts (UI delegate , auf deutsch „Oberflächenbeauftragter“ mit der Basisklasse »javax.swing.plaf.ComponentUI«) auf (falls dies nicht Null ist). Eine Swing -Komponente delegiert das Erscheinungsbild und Interaktionsverhalten (look and feel, look & feel‚ LAF ) an solch ein Oberflächenobjekt (sie verwendet also das Oberflächenobjekt um Erscheinungsbild und Interaktionsverhalten zu realisieren).
.------------------------------.
| javax.swing.plaf.ComponentUI |
'------------------------------'
^
| «Delegates»
.------------------------------.
| javax.swing.JComponent |
'------------------------------'
Die Methode "paintComponent( Graphics g )" kann in einer Unterklasse überschrieben werden, um die Erscheinung einer Komponente selber zu bestimmen. Diese Technik wird zum Zeichnen mit Swing verwendet.
Entspricht das Überschreiben der Spezifikation?
Dabei wird die oben zitierte Spezifikation der Operation aber nicht mehr korrekt implementiert, denn die Operation "paint" des Oberflächenbeauftragen wird ersetzt und nicht aufgerufen, wie in der Spezifikation beschrieben. Daher ist ein solches Vorgehen fehlerhaft, wenn man die obige Spezifikation als Beschreibung der Schnittstelle versteht, die ein Objekt der Klasse "JComponent" haben soll. Trotzdem ist dies das übliche Verfahren, das vom Hersteller auch so beabsichtigt ist. Die Dokumentation des Herstellers kann hier nicht als strenge Spezifikation der Schnittstelle gelesen werden: Sie vermischt die Beschreibung der Schnittstelle einer Klasse mit der Beschreibung des Verhaltens der Implementation der Schnittstelle durch die beschriebene Klasse. Übersichtlicher wäre es, wenn dies deutlich getrennt wäre.
Jedenfalls implementiert eine Erweiterung faktisch ein Teil der Oberflächenzeichnung, wenn sie diese Operation implementiert, und sollte damit konzeptuell diese Zeichnung so machen, wie dies dem gerade eingestellten Erscheinungsbild und Interaktionsverhalten entspricht.
Verstreute Spezifikation
Weiter oben wurde beschrieben, daß die Spezifikation der Operation "paintComponent" zu dem hier beschriebenen Überschreiben nicht ganz paßt. Allerdings kann man dieses Überschreiben besser rechtfertigen und verstehen, wenn man zur Beschreibung dieser Operation nicht nur die Stelle zählt, welche direkt unter der Überschrift "paintComponent" steht, sondern auch noch das, was in anderen Teilen der Spezifikation über dieser Operation gesagt wird.
In der Beschreibung der Operation "JComponent#paint( Graphics )" wird erklärt: „Diese Methode delegiert die Arbeit des Zeichnens an drei protected-Operationen: An die Operation "paintComponent", die Operation "paintBorder" und die Operation "paintChildren".
- paint-Implementation
paint
:
.':'.
.' : '. |
.' : '. v Delegation
.' : '.
.' : '. ->
1. .' 2. : 3. '. zeitliche
.' : '. Reihenfolge
.' : '.
paintComponent -> paintBorder -> paintChildren
:
:
: if nonzero
:
:
ui.update
Diese Operationen werden in der genannten Reihenfolge aktiviert, um sicherzustellen, daß die Kinder vor der Komponente erscheinen. Im allgemeinen sollen eine Komponente und ihre Kinder auch nicht in den Randbereich zeichnen, der dem Rahmen zugeordnet wurde. Erweiterungen können diese Methoden—wie immer—überschreiben. Eine Erweiterung, welche die paint-Operation des für das Zeichnen verantwortlichen Oberflächenbeauftragten spezialisieren will, sollte einfach paintComponent überschreiben. “ (Hier endet die Wiedergabe der Sun -Beschreibung der Operation "paint".)
Ein entscheidender Teil der Spezifikation der Operation "paintComponent" wird also nicht unter der Überschrift "paintComponent", sondern unter der Überschrift "paint" gegeben. Dieser Teil beschreibt diese Operation eher wie ein Ereignis, indem er nicht spezifiziert, was die Operation macht, sondern unter welchen Umständen sie aktiviert wird. Jedenfalls stellt er die offizielle „Lizenz“ dar, die es gestattet, diese Operation wie üblich zu überschreiben, während der Teil der Dokumentation unter der Überschrift "paintComponent" dafür nicht unbedingt ausreichen würde.
Auch noch bei der Beschreibung anderer Operationen dieser Klasse finden sich interessante Erwähnungen der Operation "paintComponent". Aus diesem Sachverhalt kann man folgendes lernen:
Um die Beschreibung einer Operation im Sun -Handbuch der Java -Klassen zu finden, reicht es nicht immer die Beschreibung dieser Operation unter der entsprechenden Überschrift zu lesen. Besser ist es, auch noch nach anderen Stellen zu suchen, an denen diese Operation erwähnt wird.
Ein Beispiel
Die Übersetzungseinheit "CustomPainting.java" zeigt das Überschreiben der Methode "paintComponent" an einem einfachen Beispiel. Die Klasse "JComponent1" legt in ihrem Konstruktor eine bevorzugte Größe fest. Die Eigenschaft "Opaque" wird auf den Wert "false" gesetzt. Dadurch wird klargestellt, daß diese Komponenten nicht garantiert, daß sie alle ihre Pixel selber zeichnet.
Die Methode "paintComponent" wird dann mit dem Methodenmodifizierer "protected" definiert, dieser Modifizierer ist nötig, weil sie auch in der Oberklasse so definiert wurde. Die Methode "paintComponent" macht hier gar nichts, weil hier zunächst ein minimales Beispiel vorgestellt werden soll.
Dementsprechend bleibt das Innere der Spezialkomponente leer und das Fenster muß zunächst vom Bediener vergrößert werden, um den Inhalt der Zeichenfläche zu sehen.
CustomPainting.java
class Canvas extends javax.swing.JFrame
{ public Canvas(){ super( "Canvas" ); }
protected void paintComponent( java.awt.Graphics g ){}}
public class CustomPainting
{ public static void main( final java.lang.String[] args )
{ javax.swing.SwingUtilities.invokeLater( new CanvasController() ); }}
class CanvasView
{ CanvasController controller = null;
Canvas canvas = null;
public CanvasView()
{ javax.swing.JFrame.setDefaultLookAndFeelDecorated( true );
canvas = new Canvas( "Canvas" );
canvas.setDefaultCloseOperation
( javax.swing.JFrame.DO_NOTHING_ON_CLOSE );
canvas.pack(); canvas.setVisible( true ); }
public void setController
( final CanvasController controller )
{ this.controller = controller;
this.controller.setView( this );
canvas.addWindowListener( this.controller ); }
public void releaseController()
{ this.controller.releaseView();
this.controller = null; }
public void dispose()
{ this.canvas.dispose(); this.canvas = null; }}
class CanvasController
implements java.lang.Runnable,
java.awt.event.WindowListener
{ CanvasView view = null;
public void setView( final CanvasView view )
{ this.view = view; }
public void releaseView()
{ this.view = null; }
public void run()
{ CanvasView view = new CanvasView();
view.setController( this ); }
public void windowClosing
( final java.awt.event.WindowEvent e )
{ CanvasView view = this.view;
view.releaseController();
view.dispose(); }
public void windowOpened( final java.awt.event.WindowEvent e ){}
public void windowDeactivated( final java.awt.event.WindowEvent e ){}
public void windowDeiconified( final java.awt.event.WindowEvent e ){}
public void windowIconified( final java.awt.event.WindowEvent e ){}
public void windowActivated( final java.awt.event.WindowEvent e ){}
public void windowClosed( final java.awt.event.WindowEvent e){} }Fenster (nach manuellem Vergrössern)
.----------------------------.
| O Canvas ::::::::::: o O X |
| |
| |
| |
| |
'----------------------------'CustomPainting.java
class Look
{ public void paint( java.awt.Graphics g, javax.swing.JComponent c )
{ g.setColor( c.getForeground() );
g.fillRect( 40, 40, 60, 60 ); }}
class Canvas extends javax.swing.JFrame
{ Look look = new Look();
public Canvas(){ super( "Canvas" ); }
protected void paintComponent( java.awt.Graphics g )
{ //super.paintComponent();
java.awt.Graphics scratchGraphics = g.create();
try { look.paint( scratchGraphics, this ); }
finally { scratchGraphics.dispose(); }}
}
public class CustomPainting
{ public static void main( final java.lang.String[] args )
{ javax.swing.SwingUtilities.invokeLater( new CanvasController() ); }}
class CanvasView
{ CanvasController controller = null;
Canvas canvas = null;
public CanvasView()
{ javax.swing.JFrame.setDefaultLookAndFeelDecorated( true );
canvas = new Canvas();
canvas.setDefaultCloseOperation
( javax.swing.JFrame.DO_NOTHING_ON_CLOSE );
canvas.pack(); canvas.setVisible( true ); }
public void setController // MVC "model:controller:"
( final CanvasController controller )
{ this.controller = controller;
this.controller.setView( this );
canvas.addWindowListener( this.controller ); }
public void releaseController() /* MVC "release" */
{ this.controller.releaseView();
this.controller = null; }
public void dispose()
{ this.canvas.dispose(); this.canvas = null; }}
class CanvasController
implements java.lang.Runnable,
java.awt.event.WindowListener
{ CanvasView view = null;
public void setView( final CanvasView view )
{ this.view = view; }
public void releaseView()
{ this.view = null; }
public void run()
{ CanvasView view = new CanvasView();
view.setController( this ); }
public void windowClosing
( final java.awt.event.WindowEvent e )
{ CanvasView view = this.view;
view.releaseController();
view.dispose(); }
public void windowOpened( final java.awt.event.WindowEvent e ){}
public void windowDeactivated( final java.awt.event.WindowEvent e ){}
public void windowDeiconified( final java.awt.event.WindowEvent e ){}
public void windowIconified( final java.awt.event.WindowEvent e ){}
public void windowActivated( final java.awt.event.WindowEvent e ){}
public void windowClosed( final java.awt.event.WindowEvent e){} }Fenster
.----------------------------.
| O Custom Painting :: o O X |
| |
| |
| |
| |
'----------------------------'
Die Wahl einer geeigneten Basisklasse
Als Ausgangspunkt für Erweiterungen bietet sich die Klasse »javax.swing.JComponent« und die Klasse »javax.swing.JPanel« an.
Die zweite hat einen Oberflächenbeauftragten (UI-delegate ), der einen eventuell gewünschten Hintergrund (nach »super.paintComponent()« in der Erweiterung) zeichnen kann. Falls man jedoch den Hintergrund ohnehin selber zeichnen will kann man die Klasse »javax.swing.JComponent« erweitern. Gegen die Erweiterung der Klasse »javax.swing.JPanel« spricht auch deren „Zugänglichkeitsrolle“ »PANEL«, die als Aufgabe die Zusammenfassung von Objekten festlegt, während »javax.swing.JComponent« eine Komponente ohne solch eine Einschränkung ist. Da eine Zeichnung erstellt werden soll und nicht andere Objekte zusammengefaßt werden sollen, paßt die Zugänglichkeitsrolle »PANEL« nicht. Entsprechend seiner Zugänglichkeitsrolle enthält »javax.swing.JPanel« auch eine Anordnungsstrategie (flow layout ) die beim Zeichnen nicht benötigt wird.
Eine Zeichenfläche erweitert die Klasse »javax.swing.JComponent«. Sie muß dann allerdings ihren Hintergrund selber zeichnen und hat keine Anordnungsstrategie für Unterkomponenten.
Da man mit einer Zeichnung ein Erscheinungsbild implementiert, sollte man dabei auch die von der Umgebung für Erscheinungsbilder festgelegten Richtlinien beachten, wie sie durch Anfragen an die Klasse »javax.swing.UIManager« ermittelt werden können.
Kommentar 25 Klasse »UIDelegate« (2008-02-17)
Von Hans F 〈Rest des Nachnamens 〉
Zu https://www.purl.org/stefan_ram/pub/java_swing_paintcomponent_de
Frage zur Seite https://userpage.fu-berlin.de/~ram/pub/pub_jf47ht53Ht/java_swing_paintcomponent_de
Hallo,
ich glaube so eine Klasse UIDelegate gibts nicht. Wenn man danach auf Sunseiten sucht, findet man solche Einträge:
ComponentUI: The base class for all UI delegate objects in the Swing pluggable look and feel architecture.
mfg
Antwort 2008-02-19 Sehr geehrter Herr F 〈Rest des Nachnamens 〉,
- Ich danke für Ihren Hinweis!
Tatsächlich gibt es in Java SE keine Klasse »UIDelegate«. Mit “UI delegate ” soll vielmehr ein „Oberflächenbeauftragter“ bezeichnet werden. “UI delegate ” ist die englische Bezeichnung dafür. Die Klasse solch eines Oberflächenbeauftragten ist in Swing »javax.swing.plaf.ComponentUI« oder eine Unterklasse dieser Klasse. Ich habe die Webseite nun etwas überarbeitet, um dies deutlicher zu machen.
Mit freundlichen Grüßen
Stefan Ram
Frage 20 Anweisung »super.paintComponent();« (2008-02-17)
Von Hans F 〈Rest des Nachnamens 〉
Zu https://www.purl.org/stefan_ram/pub/java_swing_paintcomponent_de
Noch eine Frage.
Warum kommentierst du die Zeile aus? Das funktioniert bei mir wunderbar.
protected void paintComponent( java.awt.Graphics g )
{ //super.paintComponent();
weitere Funktionen....
Man gibt die paintComponent einfach weiter an die Superklasse, die kümmert sich um die richtige Darstellung und fügt seine eigenen Sachen hinzu.
Ohne super.paintComponent funktionierts nicht richtig.
〈Netzpostadresse 〉 falls du antworten möchtest.
Antwort 2008-02-19 Sehr geehrter Herr F 〈Rest des Nachnamens 〉,
- Ich danke für Ihre Frage!
Je nachdem, ob eine Erweiterung eine Grundlage von ihrer Oberklasse übernehmen oder alles selber zeichnen will, kann dieser Aufruf passend oder nicht passend sein. Allerdings sollte ich später besser »JComponent« statt »JFrame« für Beispiele nutzen, da die erste Klasse eine bessere Grundlage für Erweiterungen darstellt.
Mit freundlichen Grüßen
Stefan Ram